├── .env.example ├── .eslintrc.js ├── .github └── workflows │ ├── production.yml │ └── staging.yml ├── .gitignore ├── .husky └── commit-msg ├── App.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __mocks__ └── axios.js ├── __tests__ ├── app.js └── smoke.spec.ts ├── app.config.js ├── app ├── features │ └── messages │ │ ├── MessagesComponent.tsx │ │ ├── __tests__ │ │ └── MessagesSagas.ts │ │ ├── messagesHelpers.ts │ │ ├── messagesSagas.ts │ │ └── messagesSlice.ts ├── screens │ ├── ErrorScreen │ │ ├── ErrorBoundary.tsx │ │ └── ErrorDetails.tsx │ └── Main.tsx ├── services │ └── api.ts ├── store │ ├── index.ts │ └── rootSaga.ts └── utils │ └── newId.ts ├── assets ├── adaptive-icon.png ├── context.ts ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── commitlint.config.js ├── package-lock.json ├── package.json ├── release.config.js └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | HUGGINGFACE_KEY="xxxx" -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["universe/native", "universe/shared/typescript-analysis"], 3 | 4 | overrides: [ 5 | { 6 | files: ["*.ts", "*.tsx", "*.d.ts"], 7 | 8 | parserOptions: { 9 | project: "./tsconfig.json", 10 | }, 11 | }, 12 | ], 13 | 14 | env: { 15 | node: true, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/production.yml: -------------------------------------------------------------------------------- 1 | name: Production Test, Lint, and Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 🏗 Setup repo 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: "0" 16 | 17 | - name: 🏗 Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 16 21 | cache: 'npm' 22 | 23 | - name: Install dependencies 24 | run: npm ci 25 | 26 | - name: Install semantic-release extra plugins 27 | run: npm install --save-dev @semantic-release/changelog @semantic-release/git 28 | 29 | - name: Format and Lint 30 | run: | 31 | npm run format 32 | npm run lint 33 | 34 | - name: Test with Jest 35 | run: npm run test 36 | 37 | - name: Bump Version and Release on Github 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | run: npx semantic-release 41 | -------------------------------------------------------------------------------- /.github/workflows/staging.yml: -------------------------------------------------------------------------------- 1 | name: Staging Test, Lint, and Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - staging 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 🏗 Setup repo 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: "0" 16 | 17 | - name: 🏗 Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 16 21 | cache: 'npm' 22 | 23 | - name: Install dependencies 24 | run: npm ci 25 | 26 | - name: Install semantic-release extra plugins 27 | run: npm install --save-dev @semantic-release/changelog @semantic-release/git 28 | 29 | - name: Format and Lint 30 | run: | 31 | npm run format 32 | npm run lint 33 | 34 | - name: Test with Jest 35 | run: npm run test 36 | 37 | - name: Bump Version and Release on Github 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | run: npx semantic-release 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | .env -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { Provider } from "react-redux"; 2 | 3 | import { ErrorBoundary } from "./app/screens/ErrorScreen/ErrorBoundary"; 4 | import Main from "./app/screens/Main"; 5 | import { store } from "./app/store"; 6 | 7 | export default function App() { 8 | return ( 9 | 10 | 11 |
12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [1.5.0](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.4.0...v1.5.0) (2022-12-04) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * remove no-irregular-whitespace ([a37d059](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a37d059cd8cf1ef7aeef833f19a7a633cd349e96)) 7 | 8 | 9 | ### Features 10 | 11 | * change context to Nintendo history ([c91f1b3](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/c91f1b34fa3ced7dc5984aea8bb46f8a25192f2d)) 12 | * change context to Nintendo history ([415f63f](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/415f63f363bcf80ab446a13dcc02b0962e4c7f7c)) 13 | 14 | # [1.5.0-staging.1](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.4.0...v1.5.0-staging.1) (2022-12-04) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * remove no-irregular-whitespace ([a37d059](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a37d059cd8cf1ef7aeef833f19a7a633cd349e96)) 20 | 21 | 22 | ### Features 23 | 24 | * change context to Nintendo history ([c91f1b3](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/c91f1b34fa3ced7dc5984aea8bb46f8a25192f2d)) 25 | * change context to Nintendo history ([415f63f](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/415f63f363bcf80ab446a13dcc02b0962e4c7f7c)) 26 | 27 | # [1.4.0](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.3.0...v1.4.0) (2022-12-03) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * add try catch to API call in saga ([71f6a92](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/71f6a92de3898314e1283a2edd92354b09aeb003)) 33 | * only cancel API when MessagesComponent unmounts ([5b5b2ef](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/5b5b2ef5561c3d397a2c629d133fd87312784a83)) 34 | 35 | 36 | ### Features 37 | 38 | * add axios validateStatus < 500 ([366d879](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/366d87916a255a9614cc8e78839b3038acf15b0a)) 39 | * add react error boundary ([dee627e](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/dee627ed8b63f3d2c626605ca2143805371627d0)) 40 | * add validateStatus for status code < 500 ([ce4a6ae](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/ce4a6ae3ce3c3e0202c2688069c29eb2aa3cbc96)) 41 | * cancel API requests when components unmount ([6b99ac2](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/6b99ac27c0a3f7815a45c0f36bd75f2f5f487b47)) 42 | 43 | # [1.3.0-staging.4](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.3.0-staging.3...v1.3.0-staging.4) (2022-12-03) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * add try catch to API call in saga ([71f6a92](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/71f6a92de3898314e1283a2edd92354b09aeb003)) 49 | 50 | # [1.3.0-staging.3](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.3.0-staging.2...v1.3.0-staging.3) (2022-12-03) 51 | 52 | 53 | ### Features 54 | 55 | * add axios validateStatus < 500 ([366d879](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/366d87916a255a9614cc8e78839b3038acf15b0a)) 56 | * add react error boundary ([dee627e](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/dee627ed8b63f3d2c626605ca2143805371627d0)) 57 | 58 | # [1.3.0-staging.2](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.3.0-staging.1...v1.3.0-staging.2) (2022-12-03) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * only cancel API when MessagesComponent unmounts ([5b5b2ef](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/5b5b2ef5561c3d397a2c629d133fd87312784a83)) 64 | 65 | 66 | ### Features 67 | 68 | * add validateStatus for status code < 500 ([ce4a6ae](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/ce4a6ae3ce3c3e0202c2688069c29eb2aa3cbc96)) 69 | * cancel API requests when components unmount ([6b99ac2](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/6b99ac27c0a3f7815a45c0f36bd75f2f5f487b47)) 70 | 71 | # [1.3.0](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.2.0...v1.3.0) (2022-12-01) 72 | 73 | 74 | ### Features 75 | 76 | * add textInputStyle props to Gifted Chat for background color ([1e72ee9](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/1e72ee94eee513bc9be6e21ef9aa016e8e6713c6)) 77 | 78 | # [1.3.0-staging.1](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.2.0...v1.3.0-staging.1) (2022-12-01) 79 | 80 | 81 | ### Features 82 | 83 | * add textInputStyle props to Gifted Chat for background color ([1e72ee9](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/1e72ee94eee513bc9be6e21ef9aa016e8e6713c6)) 84 | 85 | # [1.2.0](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.1.0...v1.2.0) (2022-11-24) 86 | 87 | 88 | ### Features 89 | 90 | * add isTyping to chat ([21cecfe](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/21cecfe8f2529b84e80c4c86ec277bbe79cfaa56)) 91 | 92 | # [1.2.0-staging.1](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.1.0...v1.2.0-staging.1) (2022-11-24) 93 | 94 | 95 | ### Features 96 | 97 | * add isTyping to chat ([21cecfe](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/21cecfe8f2529b84e80c4c86ec277bbe79cfaa56)) 98 | 99 | # [1.1.0](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.0.0...v1.1.0) (2022-11-24) 100 | 101 | 102 | ### Features 103 | 104 | * add axios-retry ([b9a6b1e](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/b9a6b1ee8d8723ed9b9cadd4d2b0cbe37c4a1e7f)) 105 | * add axios-retry ([b6b6418](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/b6b641830dab877e503e7ae80b0bb63822c292bb)) 106 | 107 | # [1.1.0-staging.1](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.0.0...v1.1.0-staging.1) (2022-11-24) 108 | 109 | 110 | ### Features 111 | 112 | * add axios-retry ([b9a6b1e](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/b9a6b1ee8d8723ed9b9cadd4d2b0cbe37c4a1e7f)) 113 | * add axios-retry ([b6b6418](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/b6b641830dab877e503e7ae80b0bb63822c292bb)) 114 | 115 | # [1.0.0-staging.3](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.0.0-staging.2...v1.0.0-staging.3) (2022-11-24) 116 | 117 | 118 | ### Features 119 | 120 | * add axios-retry ([b6b6418](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/b6b641830dab877e503e7ae80b0bb63822c292bb)) 121 | 122 | # 1.0.0 (2022-11-24) 123 | 124 | 125 | ### Bug Fixes 126 | 127 | * add error types ([b42cee5](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/b42cee51c9ef1b790912acd775d0cbb4af8a6841)) 128 | * add replyMessages type IMessage ([a2092a6](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a2092a60b924810a36e6da49490d57128dc8526f)) 129 | * add useCallback to onSend ([ed21cf9](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/ed21cf9ce4b2a6b225d9b890069bdb49d04023f7)) 130 | * delete files in wrong directory ([2ece434](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/2ece434ff3ff81d9bf79177f7388f533b96f6bd6)) 131 | * remove console.log ([133f91c](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/133f91c656864fc277f6b306306df10971e170b0)) 132 | * remove unused import ([a964dd4](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a964dd4b034b7fee9447c4936ab05f96759db146)) 133 | 134 | 135 | ### Features 136 | 137 | * add avatar png ([4554907](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/4554907316de50f560ff67e113de1399b09966a0)) 138 | * add avatar png ([5abba79](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/5abba79ad4f122222e50cd836e022f5e1cfe4b17)) 139 | * add error handling to API ([d69ee0f](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/d69ee0fe51cf57669ad69cf58cc9e12fdb733df9)) 140 | * add Gifted Chat and Redux Saga ([823f886](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/823f8860278b01e7d0ea24e2ea563126323f80a5)) 141 | * add Gifted Chat and Redux Saga ([fd106e8](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/fd106e82f334b9bdfa9350ddbcb53f300c79787c)) 142 | * add huggingface API ([a71d3cd](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a71d3cd1a47e7457eabaa3b748256e3e11b3b9e2)) 143 | * add placeholder text ([c814a9e](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/c814a9e7361ac66519dcd0ec46a4359dffc6846c)) 144 | * add SafeAreaView ([ccd18c2](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/ccd18c2aa30c3dfebdfe7c4566cdf5eb684918ac)) 145 | * use gravatar for avatars ([92cd512](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/92cd5127be0ed481e7ef4eb01fc2316c3b38e2d0)) 146 | 147 | # [1.0.0-staging.2](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/compare/v1.0.0-staging.1...v1.0.0-staging.2) (2022-11-24) 148 | 149 | 150 | ### Bug Fixes 151 | 152 | * delete files in wrong directory ([2ece434](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/2ece434ff3ff81d9bf79177f7388f533b96f6bd6)) 153 | 154 | # 1.0.0-staging.1 (2022-11-24) 155 | 156 | 157 | ### Bug Fixes 158 | 159 | * add error types ([b42cee5](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/b42cee51c9ef1b790912acd775d0cbb4af8a6841)) 160 | * add replyMessages type IMessage ([a2092a6](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a2092a60b924810a36e6da49490d57128dc8526f)) 161 | * add useCallback to onSend ([ed21cf9](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/ed21cf9ce4b2a6b225d9b890069bdb49d04023f7)) 162 | * remove console.log ([133f91c](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/133f91c656864fc277f6b306306df10971e170b0)) 163 | * remove unused import ([a964dd4](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a964dd4b034b7fee9447c4936ab05f96759db146)) 164 | 165 | 166 | ### Features 167 | 168 | * add avatar png ([4554907](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/4554907316de50f560ff67e113de1399b09966a0)) 169 | * add avatar png ([5abba79](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/5abba79ad4f122222e50cd836e022f5e1cfe4b17)) 170 | * add error handling to API ([d69ee0f](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/d69ee0fe51cf57669ad69cf58cc9e12fdb733df9)) 171 | * add Gifted Chat and Redux Saga ([823f886](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/823f8860278b01e7d0ea24e2ea563126323f80a5)) 172 | * add Gifted Chat and Redux Saga ([fd106e8](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/fd106e82f334b9bdfa9350ddbcb53f300c79787c)) 173 | * add huggingface API ([a71d3cd](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/a71d3cd1a47e7457eabaa3b748256e3e11b3b9e2)) 174 | * add placeholder text ([c814a9e](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/c814a9e7361ac66519dcd0ec46a4359dffc6846c)) 175 | * add SafeAreaView ([ccd18c2](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/ccd18c2aa30c3dfebdfe7c4566cdf5eb684918ac)) 176 | * use gravatar for avatars ([92cd512](https://github.com/kurtvandusen/React-Native-Easy-Chatbot/commit/92cd5127be0ed481e7ef4eb01fc2316c3b38e2d0)) 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kurt VanDusen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Native-Easy-Chatbot 2 | 3 | A chatbot demo app using Huggingface inference API, React Native, Expo, and Redux Sagas. Build for Android, iOS, and Web. 4 | 5 | ![Supports Expo iOS](https://img.shields.io/badge/iOS-4630EB.svg?style=flat-square&logo=APPLE&labelColor=999999&logoColor=fff) 6 | ![Supports Expo Android](https://img.shields.io/badge/Android-4630EB.svg?style=flat-square&logo=ANDROID&labelColor=A4C639&logoColor=fff) 7 | ![Supports Expo Web](https://img.shields.io/badge/Web-4630EB.svg?style=flat-square&logo=GOOGLE-CHROME&labelColor=A4C639&logoColor=fff) 8 | [![runs with Expo Go](https://img.shields.io/badge/Runs%20with%20Expo%20Go-4630EB.svg?style=flat-square&logo=EXPO&labelColor=f3f3f3&logoColor=000)](https://expo.dev/client) 9 | 10 | Based on: 11 | 12 | - Expo SDK `46` React Native for Android, iOS, Web and Expo Go 13 | - [Huggingface Question Answering API](https://huggingface.co/deepset/tinyroberta-squad2) open source machine learning model 14 | - [Redux-Saga](https://redux-saga.js.org/) Async middleware for Redux 15 | - [Ignite from Infinite Red](https://github.com/infinitered/ignite) React Native Boilerplate 16 | 17 | ## Features 18 | 19 | - The API is called using Redux-Saga for clear, testable async logic 20 | - Axios with Axios-Retry for data fetching 21 | - API error handling 22 | - React Error Boundary 23 | - ESLint and Prettier to standarize formating 24 | - Husky for conventional commits 25 | - Jest for unit testing 26 | - Semantic Release to bump versions and automatically generate [CHANGELOG.md](./CHANGELOG.md) 27 | - Github Actions for CI/CD 28 | - [Gifted Chat](https://github.com/FaridSafi/react-native-gifted-chat) for Material Design and accesability 29 | - Reusable chat component 30 | 31 | ## Installation 32 | 33 | ```sh 34 | git clone https://github.com/kurtvandusen/React-Native-Easy-Chatbot 35 | cd React-Native-Easy-Chatbot 36 | npm i 37 | ``` 38 | 39 | ## Optional - Create .env 40 | 41 | In the root directory, create a new file named .env and copy and paste the contents from .env.example. Then replace the example value with your own Huggingface API key. 42 | 43 | [Create a huggingface.co account to get your free API key](https://huggingface.co/) 44 | 45 | ## Optional - Customize Constants in app.config.js 46 | 47 | In the root directory, locate the app.config.js file. Edit the ` extra: {}` section to customize the values the will be used by Expo-Constants for 48 | - huggingfaceKey 49 | - baseURL 50 | - messagesPlaceholder 51 | - messagesErrorMessage 52 | 53 | ## Optional - Change baseURL to use a different Question Answering NLP model 54 | 55 | [Explore more Question Answering NLP models on huggingface](https://huggingface.co/models?pipeline_tag=question-answering&sort=downloads&search=question+answering) 56 | 57 | Simply change your baseURL to use a different model. 58 | 59 | ## Optional - Change Question Answering Context 60 | 61 | Update [context.ts](./assets/context.ts) with the text string of your choice. The chatbot will draw all answers from this text only. The current example context file contains text about the history of Nintendo Co, Ltd. drawn from [Wikipedia](https://en.wikipedia.org/wiki/Nintendo) 62 | 63 | ## Unit Testing Sagas 64 | 65 | [Here is an example of testing the saga async logic using Jest](./app/features/messages/__tests__/MessagesSagas.ts) -------------------------------------------------------------------------------- /__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | import mockAxios from 'jest-mock-axios'; 2 | export default mockAxios; -------------------------------------------------------------------------------- /__tests__/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import App from '../App'; 5 | 6 | describe('', () => { 7 | it('has 1 child', () => { 8 | const tree = renderer.create().toJSON(); 9 | expect(tree.children.length).toBe(1); 10 | }); 11 | }); -------------------------------------------------------------------------------- /__tests__/smoke.spec.ts: -------------------------------------------------------------------------------- 1 | describe('truth', () => { 2 | it('is true', () => { 3 | expect(true).toEqual(true); 4 | }); 5 | }); -------------------------------------------------------------------------------- /app.config.js: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import * as pkg from "./package.json"; 3 | 4 | export default { 5 | expo: { 6 | name: "Easy-Chatbot", 7 | slug: "Easy-Chatbot", 8 | version: pkg.version, 9 | orientation: "portrait", 10 | icon: "./assets/icon.png", 11 | userInterfaceStyle: "light", 12 | splash: { 13 | image: "./assets/splash.png", 14 | resizeMode: "contain", 15 | backgroundColor: "#ffffff", 16 | }, 17 | updates: { 18 | fallbackToCacheTimeout: 0, 19 | }, 20 | assetBundlePatterns: ["**/*"], 21 | ios: { 22 | supportsTablet: true, 23 | }, 24 | android: { 25 | adaptiveIcon: { 26 | foregroundImage: "./assets/adaptive-icon.png", 27 | backgroundColor: "#FFFFFF", 28 | }, 29 | }, 30 | web: { 31 | favicon: "./assets/favicon.png", 32 | }, 33 | extra: { 34 | huggingfaceKey: 35 | process.env.HUGGINGFACE_KEY ?? "getYourFreeKeyForUnlimitedCalls", 36 | baseURL: 37 | "https://api-inference.huggingface.co/models/deepset/tinyroberta-squad2", 38 | messagesPlaceholder: "Ask about Nintendo history...", 39 | messagesErrorMessage: "Sorry, there was an error. " 40 | }, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /app/features/messages/MessagesComponent.tsx: -------------------------------------------------------------------------------- 1 | import Constants from "expo-constants"; 2 | import React, { useCallback, useEffect } from "react"; 3 | import { StyleProp, TextStyle } from "react-native"; 4 | import { GiftedChat } from "react-native-gifted-chat"; 5 | import { useSelector, useDispatch } from "react-redux"; 6 | 7 | import { controller } from "../../services/api"; 8 | import { RootState } from "../../store"; 9 | import { sendMessage, defaultUser } from "./messagesSlice"; 10 | 11 | export interface MessagesComponentProps { 12 | textInputStyle?: StyleProp; 13 | } 14 | 15 | const placeholder = Constants.expoConfig.extra?.messagesPlaceholder ?? ""; 16 | 17 | export default function MessagesComponent({ 18 | textInputStyle, 19 | }: MessagesComponentProps) { 20 | const dispatch = useDispatch(); 21 | const messages = useSelector((state: RootState) => state.messages.messages); 22 | const isTyping = useSelector((state: RootState) => state.messages.isTyping); 23 | 24 | useEffect(() => { 25 | return () => { 26 | /* cancel API requests when component unmounts*/ 27 | controller.abort(); 28 | console.log("message component unmounted"); 29 | }; 30 | }, []); 31 | 32 | const onSend = useCallback((messages = []) => { 33 | dispatch({ type: sendMessage.toString(), payload: messages[0] }); 34 | }, []); 35 | 36 | return ( 37 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /app/features/messages/__tests__/MessagesSagas.ts: -------------------------------------------------------------------------------- 1 | import { put, call } from "redux-saga/effects"; 2 | 3 | import { createReplyMessage, getReply } from "../messagesHelpers"; 4 | import { sendMessageStart } from "../messagesSagas"; 5 | import { setMessages, setIsTyping } from "../messagesSlice"; 6 | 7 | describe("test messagesSagas", () => { 8 | it("Test saga sendMessageStart", () => { 9 | const testMessage = createReplyMessage("test"); 10 | const generator = sendMessageStart({ payload: testMessage }); 11 | expect(generator.next().value).toEqual( 12 | put({ type: setIsTyping, payload: true }) 13 | ); 14 | expect(generator.next().value).toEqual( 15 | put({ type: setMessages, payload: testMessage }) 16 | ); 17 | expect(generator.next().value).toEqual(call(getReply, testMessage)); 18 | expect(generator.next().value).toEqual( 19 | put({ type: setMessages, payload: undefined }) 20 | ); 21 | expect(generator.next().value).toEqual( 22 | put({ type: setIsTyping, payload: false }) 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/features/messages/messagesHelpers.ts: -------------------------------------------------------------------------------- 1 | import { IMessage } from "react-native-gifted-chat"; 2 | 3 | import { context } from "../../../assets/context"; 4 | import { api } from "../../services/api"; 5 | import { newId } from "../../utils/newId"; 6 | import { chatbotUser } from "./messagesSlice"; 7 | 8 | export const createReplyMessage = (reply: string) => { 9 | const replyMessage: IMessage = { 10 | _id: newId(), 11 | text: reply, 12 | createdAt: new Date(), 13 | user: chatbotUser, 14 | }; 15 | return replyMessage; 16 | }; 17 | 18 | export const getReply = async (message: IMessage) => { 19 | const body = { 20 | inputs: { 21 | question: message.text, 22 | context, 23 | }, 24 | options: { 25 | wait_for_model: true, 26 | }, 27 | }; 28 | const replyResponse = await api.getAnswer(body); 29 | 30 | return createReplyMessage(replyResponse.answer); 31 | }; 32 | -------------------------------------------------------------------------------- /app/features/messages/messagesSagas.ts: -------------------------------------------------------------------------------- 1 | import Constants from "expo-constants"; 2 | import { put, takeEvery, call } from "redux-saga/effects"; 3 | 4 | import { getReply, createReplyMessage } from "./messagesHelpers"; 5 | import { setMessages, sendMessage, setIsTyping } from "./messagesSlice"; 6 | 7 | // Our worker Sagas 8 | function* sendMessageStart({ payload: message }) { 9 | yield put({ type: setIsTyping, payload: true }); 10 | yield put({ type: setMessages, payload: message }); 11 | try { 12 | const replyMessages = yield call(getReply, message); 13 | yield put({ type: setMessages, payload: replyMessages }); 14 | } catch (error) { 15 | const errorMessage: string = 16 | Constants.expoConfig.extra?.messagesErrorMessage + error?.toString; 17 | yield put({ type: setMessages, payload: createReplyMessage(errorMessage) }); 18 | } 19 | yield put({ type: setIsTyping, payload: false }); 20 | } 21 | 22 | // Our watcher Sagas 23 | function* messagesSaga() { 24 | yield takeEvery(sendMessage, sendMessageStart); 25 | } 26 | 27 | export { messagesSaga, sendMessageStart }; 28 | -------------------------------------------------------------------------------- /app/features/messages/messagesSlice.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createSlice, 3 | createAction, 4 | ActionCreatorWithPayload, 5 | } from "@reduxjs/toolkit"; 6 | import type { PayloadAction } from "@reduxjs/toolkit"; 7 | import { GiftedChat, User, IMessage } from "react-native-gifted-chat"; 8 | 9 | export interface MessagesState { 10 | messages: IMessage[]; 11 | isTyping: boolean; 12 | } 13 | 14 | export const defaultUser: User = { 15 | _id: 0, 16 | name: "User", 17 | avatar: "https://gravatar.com/avatar?d=wavatar", 18 | }; 19 | 20 | export const chatbotUser: User = { 21 | _id: 1, 22 | name: "Chatbot", 23 | avatar: "https://gravatar.com/avatar?d=robohash", 24 | }; 25 | 26 | const initialState: MessagesState = { 27 | messages: [], 28 | isTyping: false, 29 | }; 30 | 31 | export const messagesSlice = createSlice({ 32 | name: "messages", 33 | initialState, 34 | reducers: { 35 | setMessages: (state: MessagesState, action: PayloadAction) => { 36 | const newState = GiftedChat.append(state.messages, [action.payload]); 37 | state.messages = newState; 38 | }, 39 | setIsTyping: (state: MessagesState, action: PayloadAction) => { 40 | state.isTyping = action.payload; 41 | }, 42 | }, 43 | }); 44 | 45 | export const sendMessage: ActionCreatorWithPayload = 46 | createAction("SEND_MESSAGE"); 47 | 48 | // Action creators are generated for each case reducer function 49 | export const { setMessages, setIsTyping } = messagesSlice.actions; 50 | 51 | export default messagesSlice.reducer; 52 | -------------------------------------------------------------------------------- /app/screens/ErrorScreen/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, ErrorInfo, ReactNode } from "react"; 2 | 3 | import { ErrorDetails } from "./ErrorDetails"; 4 | 5 | interface Props { 6 | children: ReactNode; 7 | catchErrors: "always" | "dev" | "prod" | "never"; 8 | } 9 | 10 | interface State { 11 | error: Error | null; 12 | errorInfo: ErrorInfo | null; 13 | } 14 | 15 | /** 16 | * This component handles whenever the user encounters a JS error in the 17 | * app. It follows the "error boundary" pattern in React. We're using a 18 | * class component because according to the documentation, only class 19 | * components can be error boundaries. 20 | * 21 | * - [Documentation and Examples](https://github.com/infinitered/ignite/blob/master/docs/Error-Boundary.md) 22 | * - [React Error Boundaries](https://reactjs.org/docs/error-boundaries.html) 23 | */ 24 | export class ErrorBoundary extends Component { 25 | state = { error: null, errorInfo: null }; 26 | 27 | // If an error in a child is encountered, this will run 28 | componentDidCatch(error: Error, errorInfo: ErrorInfo) { 29 | // Catch errors in any components below and re-render with error message 30 | this.setState({ 31 | error, 32 | errorInfo, 33 | }); 34 | 35 | // You can also log error messages to an error reporting service here 36 | // This is a great place to put BugSnag, Sentry, crashlytics, etc: 37 | // reportCrash(error) 38 | } 39 | 40 | // Reset the error back to null 41 | resetError = () => { 42 | this.setState({ error: null, errorInfo: null }); 43 | }; 44 | 45 | // To avoid unnecessary re-renders 46 | shouldComponentUpdate( 47 | nextProps: Readonly, 48 | nextState: Readonly 49 | ): boolean { 50 | return nextState.error !== nextProps.error; 51 | } 52 | 53 | // Only enable if we're catching errors in the right environment 54 | isEnabled(): boolean { 55 | return ( 56 | this.props.catchErrors === "always" || 57 | (this.props.catchErrors === "dev" && __DEV__) || 58 | (this.props.catchErrors === "prod" && !__DEV__) 59 | ); 60 | } 61 | 62 | // Render an error UI if there's an error; otherwise, render children 63 | render() { 64 | return this.isEnabled() && this.state.error ? ( 65 | 70 | ) : ( 71 | this.props.children 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/screens/ErrorScreen/ErrorDetails.tsx: -------------------------------------------------------------------------------- 1 | import React, { ErrorInfo } from "react"; 2 | import { ScrollView, View, Text, Button } from "react-native"; 3 | 4 | export interface ErrorDetailsProps { 5 | error: Error; 6 | errorInfo: ErrorInfo; 7 | onReset(): void; 8 | } 9 | 10 | export function ErrorDetails(props: ErrorDetailsProps) { 11 | return ( 12 | 13 | 14 | {`${props.error}`.trim()} 15 | {`${props.errorInfo.componentStack}`.trim()} 16 | 17 | 18 |