├── .github └── issue_template.md ├── .gitignore ├── .travis.yml ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples ├── basic.js ├── celebrities.js ├── joke.js ├── messenger.js ├── pizza.js ├── synthesize-speech.js ├── wit-example-celebrities.zip └── wit-example-joke-bot.zip ├── index.js ├── lib ├── config.js ├── interactive.js ├── log.js └── wit.js ├── package.json ├── publish.sh └── tests ├── dist.js ├── lib.js ├── shared.js └── wit-ai-basic-app-for-tests.zip /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a *feature*, report a *bug*, or ask a *question* about node-wit?** 2 | 3 | **What is the current behavior?** 4 | 5 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.** 6 | 7 | **What is the expected behavior?** 8 | 9 | **If applicable, what is the App ID where you are experiencing this issue? If you do not provide this, we cannot help.** 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | node_modules 3 | .DS_Store 4 | package-lock.json 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.9.0' 4 | script: 5 | - npm test 6 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## v6.6.0 - Multi-responses in Composer 2 | 3 | - Multi-responses in Composer: `runComposerAudio()`, `.runComposer()` (and raw `.converse()` + `.event()`) now emit `response` events for intermediate responses, and run intermediate actions as well. 4 | - Bumped API version to `20230215`. 5 | 6 | ## v6.5.1 7 | - Update uuid to version 9.0.0 8 | 9 | ## v6.5.0 - Composer alpha integration 10 | 11 | - Composer integration: `.runComposerAudio()`, `.runComposer()` (and raw `.converse()` + `.event()`) + `actions` support 12 | - Bumped API version to `20220801`. 13 | - interactive now uses Composer for text inputs, use `!message` for `GET /message` and `!converse` for Composer audio inputs 14 | - added pizza example 15 | 16 | ## v6.4.0 17 | 18 | - Add `POST /synthesize` integration. 19 | - Add `POST /dictation` integration. 20 | - New example using `synthesize()` and `dictation()`. 21 | 22 | ## v6.3.0 23 | 24 | - `speech()` emits `partialUnderstanding` events to support live understanding. 25 | - `apiVersion` updated to `20220608` and its type is now a number. 26 | 27 | ## Breaking changes 28 | 29 | - Bumped API version to `20220608`. 30 | - Emits `partialUnderstanding` events (live understanding support). 31 | - Updated `apiVersion` type from string to number. 32 | 33 | ## v6.2.2 34 | 35 | - Fixes parsing for large HTTP chunks. 36 | 37 | ## v6.2.1 38 | 39 | - Emits `partialTranscription` and `fullTranscription` events. 40 | - Shows microphone input feedback for `interactive`. 41 | - Includes `proxy` support for `speech()`. 42 | 43 | ## v6.2.0 44 | 45 | Requires Node.js >= 6.17.1 to support ES6 directly. 46 | 47 | ## v6.1.1 48 | 49 | - Basic `POST /speech` integration. 50 | - `!speech` support for interactive. 51 | 52 | ## v6.1.0 53 | 54 | Bumped API version to `20210928`. 55 | Moved API version from `Accept` header to `v` HTTP parameter. 56 | Kaizens. 57 | 58 | ## v6.0.1 59 | 60 | Removed unused `request` dependency 61 | Updated various dependencies. 62 | 63 | ## v6.0.0 64 | 65 | Updated API version to latest: `20200513`. 66 | Browse the latest HTTP API documentation [here](https://wit.ai/docs/http/20200513#get__message_link). 67 | 68 | ## v5.0.0 69 | 70 | The most important change is the removal of `.converse()` and `.runActions()`. Follow the migration tutorial [here](https://github.com/wit-ai/wit-stories-migration-tutorial), or [read more here](https://wit.ai/blog/2017/07/27/sunsetting-stories). 71 | 72 | ### Breaking changes 73 | 74 | - `converse` and `runActions` are removed 75 | - updated and added new examples that leverage the /message API 76 | - updated wit-ai-basic-app-for-tests.zip for testing 77 | 78 | ## v4.3.0 79 | 80 | - `converse` and `runActions` are deprecated 81 | - `interactive` now calls `message` 82 | 83 | ## v4.2.0 84 | 85 | - support actions that do not return promises 86 | - support the case where an action does not return a Promise 87 | - update uuid to version 3.0.0 88 | - Support older versions of node 89 | - 'Use strict' on interactive.js 90 | - Check for bot's message in messenger example 91 | 92 | ## v4.1.0 93 | 94 | - Support for different JS environments 95 | - `converse` now takes `reset` as an optional parameter 96 | 97 | ### Breaking changes 98 | 99 | - `interactive` is no longer a function on the `Wit` client. Instead, you require it from the library: `require('node-wit').interactive` 100 | - `runActions` now resets the last turn on new messages and errors. 101 | 102 | ## v4.0.0 103 | 104 | After a lot of internal dogfooding and bot building, we decided to change the API in a backwards-incompatible way. The changes are described below and aim to simplify user code and accommodate upcoming features. 105 | 106 | We moved to a Promise-based API, instead of callbacks. This makes the code simpler and the error-handling more straight-forward. It's also inline with where JS is going with standards like `fetch()` and `async/await` that are based on Promises. 107 | 108 | See `./examples` to see how to use the new API. 109 | 110 | ### Breaking changes 111 | 112 | - `say` renamed to `send` to reflect that it deals with more than just text 113 | - Removed built-in actions `merge` and `error` 114 | - Actions signature simplified with `request` and `response` arguments 115 | - Actions need to return promises and do not receive the `cb` parameter anymore 116 | - INFO level replaces LOG level 117 | - configuration is now done when instantiating the `Wit` object, instead of using env vars 118 | 119 | ## v3.3.2 120 | 121 | - allows for overriding API version, by setting `WIT_API_VERSION` 122 | 123 | ## v3.3.1 124 | 125 | - adding API versioning (defaults to `20160516`) 126 | - warns instead of throwing when validating actions 127 | - fixing null values when cloning context 128 | 129 | ## v3.3.0 130 | 131 | - callbacks are not called asynchronously by default, choice is left to the developer (use process.nextTick in your callback to emulate the previous behavior) 132 | - using `node-fetch` instead of `requests` 133 | - the `message()` API takes now an optional context as second parameter 134 | 135 | ## v3.2.2 136 | 137 | - fixing context not updated in interactive mode 138 | - fixing array values in context 139 | - create readline interface only in interactive mode 140 | 141 | ## v3.2.0 142 | 143 | Unifying action parameters. 144 | 145 | ### breaking 146 | 147 | - the `say` action now takes 4 parameters: `sessionId`, `context`, `message`, `cb` 148 | - the `error` action now takes 3 parameters: `sessionId`, `context`, `error` 149 | 150 | ## v3.1.0 151 | 152 | Updating action parameters. 153 | 154 | ### breaking 155 | 156 | - the `merge` action now takes 5 parameters: `sessionId`, `context`, `entities`, `message`, `cb` 157 | - the `error` action now takes the context as second parameter 158 | - custom actions now take 3 parameters: `sessionId`, `context`, `cb` 159 | 160 | ## v3.0.0 161 | 162 | Bot Engine integration 163 | 164 | ### breaking 165 | 166 | - the library now provides a Wit object 167 | - `captureTextIntent` has been moved to `Wit.message` with no token 168 | - audio not supported 169 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. 4 | Please read the [full text](https://code.fb.com/codeofconduct/) 5 | so that you can understand what actions will and will not be tolerated. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to node-wit 2 | 3 | We want to make contributing to this project as easy and transparent as 4 | possible. 5 | 6 | ## Pull Requests 7 | We actively welcome your pull requests. 8 | 9 | 1. Fork the repo and create your branch from `master`. 10 | 2. If you've added code that should be tested, add tests. 11 | 3. If you've changed APIs, update the documentation. 12 | 4. Ensure the test suite passes. 13 | 5. If you haven't already, complete the Contributor License Agreement ("CLA"). 14 | 15 | ## Contributor License Agreement ("CLA") 16 | 17 | In order to accept your pull request, we need you to submit a CLA. You only need 18 | to do this once to work on any of Facebook's open source projects. 19 | 20 | Complete your CLA here: 21 | 22 | ## Issues 23 | 24 | We use GitHub issues to track public bugs. Please ensure your description is 25 | clear and has sufficient instructions to be able to reproduce the issue. 26 | 27 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 28 | disclosure of security bugs. In those cases, please go through the process 29 | outlined on that page and do not file a public issue. 30 | 31 | ## License 32 | 33 | By contributing to node-wit, you agree that your contributions will be licensed 34 | under the LICENSE file in the root directory of this source tree. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, Wit.ai, Inc. All rights reserved. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Wit.ai. 8 | * 9 | * As with any software that integrates with the Wit.ai platform, your use 10 | * of this software is subject to the Wit.ai Terms of Service 11 | * [https://wit.ai/terms]. This copyright notice shall be included in all 12 | * copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wit Node.js SDK [![npm](https://img.shields.io/npm/v/node-wit.svg)](https://www.npmjs.com/package/node-wit) 2 | 3 | `node-wit` is the Node.js SDK for [Wit.ai](https://wit.ai). 4 | 5 | ## Install 6 | 7 | In your Node.js project, run: 8 | 9 | ```bash 10 | npm install --save node-wit 11 | ``` 12 | 13 | ## Quickstart 14 | 15 | Run in your terminal: 16 | 17 | ```bash 18 | node examples/basic.js 19 | ``` 20 | 21 | See `examples` folder for more examples. Some examples have associated .zip files, do not forget to import those [when creating a new app](https://wit.ai/apps) and grab your access token from the Settings section. 22 | 23 | ### Messenger integration example 24 | 25 | See `examples/messenger.js` for a thoroughly documented tutorial. 26 | 27 | ### Overview 28 | 29 | The Wit module provides a Wit class with the following methods: 30 | 31 | - `runComposerAudio` - the [Composer](https://wit.ai/docs/recipes#composer) integration for voice; 32 | - `runComposer` - the [Composer](https://wit.ai/docs/recipes#composer) integration for other inputs; 33 | - `converse` - the Wit [converse](https://wit.ai/docs/http/#post__converse_link) API; 34 | - `event` - the Wit [event](https://wit.ai/docs/http#post__event_link) API; 35 | - `message` - the Wit [message](https://wit.ai/docs/http#get__message_link) API; 36 | - `speech` - the Wit [speech](https://wit.ai/docs/http#post__speech_link) API; 37 | - `dictation` - the Wit [dictation](https://wit.ai/docs/http#post__dictation_link) API; 38 | - `synthesize` - the Wit [synthesize](https://wit.ai/docs/http#post__synthesize_link) API. 39 | 40 | You can also require a library function to test out your Wit app in the terminal. `require('node-wit').interactive` 41 | 42 | ### Wit class 43 | 44 | The Wit constructor takes the following parameters: 45 | 46 | - `accessToken` - the access token of your Wit instance; 47 | - `actions` - the object of [client action definitions for Composer](https://wit.ai/docs/recipes#run-custom-code); 48 | - `logger` - (optional) the object handling the logging; 49 | - `apiVersion` - (optional) the API version to use instead of the recommended one 50 | 51 | The `logger` object should implement the methods `debug`, `info`, `warn` and `error`. 52 | They can receive an arbitrary number of parameters to log. 53 | For convenience, we provide a `Logger` class, taking a log level parameter 54 | 55 | Example: 56 | 57 | ```js 58 | const {Wit, log} = require('node-wit'); 59 | 60 | const actions = { 61 | confirm_order(contextMap) { 62 | return {context_map: {...contextMap, order_confirmation: 'PIZZA42'}}; 63 | }, 64 | }; 65 | 66 | const client = new Wit({ 67 | accessToken: MY_TOKEN, 68 | actions, 69 | logger: new log.Logger(log.DEBUG), // optional 70 | }); 71 | 72 | console.log(client.message('set an alarm tomorrow at 7am')); 73 | ``` 74 | 75 | ## .runComposerAudio() 76 | 77 | The [Composer](https://wit.ai/docs/recipes#composer) integration for voice. 78 | 79 | Takes the following parameters: 80 | 81 | - `sessionId` - a unique string identifying the user session 82 | - `contentType` - the Content-Type header 83 | - `body` - the audio `Readable` stream 84 | - `contextMap` - the [context map](https://wit.ai/docs/recipes#custom-context) object 85 | 86 | Emits `partialTranscription`, `response` and `fullTranscription` events. 87 | Run the provided `actions` as instructed by the API response, and calls back with the resulting updated context map (unless the action returns `stop: true`). 88 | The Promise returns the final JSON payload of the last API call ([POST /converse](https://wit.ai/docs/http#post__converse_link) or [POST 89 | /event](https://wit.ai/docs/http#post__event_link)). 90 | 91 | See `lib/interactive.js` for an example. 92 | 93 | ## .runComposer() 94 | 95 | The [Composer](https://wit.ai/docs/recipes#composer) integration for other 96 | inputs, including text. 97 | 98 | Takes the following parameters: 99 | 100 | - `sessionId` - a unique string identifying the user session 101 | - `contextMap` - the [context map](https://wit.ai/docs/recipes#custom-context) object 102 | - `message` - the optional user text query 103 | 104 | Emits `response` events. 105 | Run the provided `actions` as instructed by the API response, and calls back with the resulting updated context map (unless the action returns `stop: true`). 106 | The Promise returns the final JSON payload of the last [POST /event](https://wit.ai/docs/http#post__event_link) API call. 107 | 108 | See `lib/interactive.js` for an example. 109 | 110 | ## .converse() 111 | 112 | The Wit [converse](https://wit.ai/docs/http/#post__converse_link) API. 113 | 114 | Takes the following parameters: 115 | 116 | - `sessionId` - a unique string identifying the user session 117 | - `contentType` - the Content-Type header 118 | - `body` - the audio `Readable` stream 119 | - `contextMap` - the [context map](https://wit.ai/docs/recipes#custom-context) object 120 | 121 | Emits `partialTranscription`, `fullTranscription`, and `response` events. Runs 122 | intermediate `actions` as instructed by the API. 123 | 124 | We recommend to use `.runComposerAudio()` instead of this raw API. 125 | 126 | ## .event() 127 | 128 | The Wit [event](https://wit.ai/docs/http#post__event_link) API. 129 | 130 | Takes the following parameters: 131 | 132 | - `sessionId` - a unique string identifying the user session 133 | - `contextMap` - the [context map](https://wit.ai/docs/recipes#custom-context) object 134 | - `message` - the optional user text query 135 | 136 | Emits `response` events, and run intermediate `actions` as instructed by the API. 137 | 138 | We recommend to use `.runComposer()` instead of this raw API. 139 | 140 | ### .message() 141 | 142 | The Wit [message](https://wit.ai/docs/http/#get__message_link) API. 143 | 144 | Takes the following parameters: 145 | 146 | - `q` - the text input you want Wit.ai to extract the information from 147 | - `context` - (optional) the [Context](https://wit.ai/docs/http/#context_link) object 148 | - `n` - (optional) the max number of intents and traits to get back 149 | 150 | Example: 151 | 152 | ```js 153 | const client = new Wit({accessToken: 'MY_TOKEN'}); 154 | client 155 | .message('what is the weather in London?', {}) 156 | .then(data => { 157 | console.log('Yay, got Wit.ai response: ' + JSON.stringify(data)); 158 | }) 159 | .catch(console.error); 160 | ``` 161 | 162 | See `lib/interactive.js` for another example integration. 163 | 164 | ### .speech() 165 | 166 | The Wit [speech](https://wit.ai/docs/http#post__speech_link) API. 167 | 168 | Takes the following paramters: 169 | 170 | - `contentType` - the Content-Type header 171 | - `body` - the audio `Readable` stream 172 | - `context` - (optional) the [Context](https://wit.ai/docs/http/#context_link) object 173 | - `n` - (optional) the max number of intents and traits to get back 174 | 175 | Emits `partialTranscription`, `partialUnderstanding` and `fullTranscription` events. 176 | The Promise returns the final JSON payload. 177 | 178 | See `lib/interactive.js` for an example. 179 | 180 | ### .dictation() 181 | 182 | The Wit [dictation](https://wit.ai/docs/http#post__dictation_link) API. 183 | 184 | Takes the following paramters: 185 | 186 | - `contentType` - the Content-Type header 187 | - `body` - the audio `Readable` stream 188 | 189 | Emits `partialTranscription`, and `fullTranscription` events. 190 | The Promise returns the final JSON payload. 191 | 192 | See `examples/synthesize-speech.js` for an example. 193 | 194 | ### .synthesize() 195 | 196 | The Wit [synthesize](https://wit.ai/docs/http#post__synthesize_link) API. 197 | 198 | Takes the following paramters (click on link above for more details): 199 | 200 | - `q` - The query containting text to synthesize 201 | - `voice` - The voice name. For voices and styles available, see GET [voices.](https://wit.ai/docs/http#get__voices_link) 202 | - `style` - (optional) The style to speak in 203 | - `speed` - (optional) the speed the text is spoken 204 | - `pitch` - (optional) the pitch of the audio 205 | - `gain` - (optional) the gain of the audio 206 | 207 | The Promise returns the final response, with the body containing the audio stream of the synthesized text. 208 | 209 | See `examples/synthesize-speech.js` for an example. 210 | 211 | ### interactive 212 | 213 | Starts an interactive conversation with your Wit app. 214 | 215 | Full conversational interactions: 216 | Use `!converse` to send an audio request from the microphone using Composer. 217 | Enter any text input to send a text request using Composer. 218 | 219 | One-off natural language requests: 220 | Use `!speech` to send an audio request from the microphone. 221 | Use `!message ` to send a text request. 222 | 223 | Example: 224 | 225 | ```js 226 | const {interactive} = require('node-wit'); 227 | interactive(client); 228 | ``` 229 | 230 | See the [docs](https://wit.ai/docs) for more information. 231 | 232 | ## Changing the API version 233 | 234 | The default (recommended, latest) API version is set in `config.js`. 235 | 236 | On May 13th, 2020, the `GET /message` API was updated to reflect the new data model: intents, traits and entities are now distinct. 237 | You can target a specific version by passing the `apiVersion` parameter when creating the `Wit` object. 238 | 239 | ```json 240 | { 241 | "text": "hello", 242 | "intents": [ 243 | { 244 | "id": "1353535345345", 245 | "name": "greet", 246 | "confidence": 0.9753 247 | } 248 | ], 249 | "entities": [], 250 | "traits": [] 251 | } 252 | ``` 253 | 254 | ## Running tests 255 | 256 | 1. Create a new app in wit.ai web console using tests/wit-ai-basic-app-for-tests.zip 257 | 2. Copy the Server Access Token from app settings 258 | 3. Run `WIT_TOKEN=XXX npm test`, where XXX is the Server Access Token 259 | 260 | ## License 261 | 262 | The license for node-wit can be found in LICENSE file in the root directory of 263 | this source tree. 264 | 265 | ## Terms of Use 266 | Our terms of use can be found at https://opensource.facebook.com/legal/terms. 267 | 268 | Use of Wit.ai services fall under the terms of use found here: https://wit.ai/terms. 269 | 270 | ## Privacy Policy 271 | Our privacy policy can be found at https://opensource.facebook.com/legal/privacy. 272 | 273 | The privacy policy for the Wit.ai service can be found at https://wit.ai/privacy. 274 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | let Wit = null; 8 | let interactive = null; 9 | try { 10 | // if running from repo 11 | Wit = require('../').Wit; 12 | interactive = require('../').interactive; 13 | } catch (e) { 14 | Wit = require('node-wit').Wit; 15 | interactive = require('node-wit').interactive; 16 | } 17 | 18 | const accessToken = (() => { 19 | if (process.argv.length !== 3) { 20 | console.log('usage: node examples/basic.js '); 21 | process.exit(1); 22 | } 23 | return process.argv[2]; 24 | })(); 25 | 26 | const client = new Wit({accessToken}); 27 | interactive(client); 28 | -------------------------------------------------------------------------------- /examples/celebrities.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | let Wit = null; 8 | let interactive = null; 9 | try { 10 | // if running from repo 11 | Wit = require('../').Wit; 12 | interactive = require('../').interactive; 13 | } catch (e) { 14 | Wit = require('node-wit').Wit; 15 | interactive = require('node-wit').interactive; 16 | } 17 | 18 | const accessToken = (() => { 19 | if (process.argv.length !== 3) { 20 | console.log('usage: node examples/celebrities.js '); 21 | console.log('Please import the corresponding .zip file upon creating an app and grab your access token from the Settings page'); 22 | process.exit(1); 23 | } 24 | return process.argv[2]; 25 | })(); 26 | 27 | // Celebrities example 28 | // See https://wit.ai/aleka/wit-example-celebrities/ 29 | 30 | const firstEntityResolvedValue = (entities, entity) => { 31 | const val = entities && entities[entity] && 32 | Array.isArray(entities[entity]) && 33 | entities[entity].length > 0 && 34 | entities[entity][0].resolved && 35 | entities[entity][0].resolved.values && 36 | Array.isArray(entities[entity][0].resolved.values) && 37 | entities[entity][0].resolved.values.length > 0 && 38 | entities[entity][0].resolved.values[0] 39 | ; 40 | if (!val) { 41 | return null; 42 | } 43 | return val; 44 | }; 45 | 46 | const firstTraitValue = (traits, trait) => { 47 | const val = traits && traits[trait] && 48 | Array.isArray(traits[trait]) && 49 | traits[trait].length > 0 && 50 | traits[trait][0].value 51 | ; 52 | if (!val) { 53 | return null; 54 | } 55 | return val; 56 | }; 57 | 58 | const handleMessage = ({entities, traits}) => { 59 | const greetings = firstTraitValue(traits, 'wit$greetings'); 60 | const celebrity = firstEntityResolvedValue(entities, 'wit$notable_person:notable_person'); 61 | if (celebrity) { 62 | // We can call wikidata API for more info here 63 | printWikidataDescription(celebrity); 64 | } else if (greetings) { 65 | console.log("Hi! You can say something like 'Tell me about Beyonce'"); 66 | } else { 67 | console.log("Umm. I don't recognize that name. Which celebrity do you want to learn about?"); 68 | } 69 | }; 70 | 71 | const printWikidataDescription = (celebrity) => { 72 | const wikidataID = celebrity.external && celebrity.external.wikidata; 73 | if (!wikidataID) { 74 | // in case wikidata id isn't available 75 | console.log(`I recognize ${celebrity.name}!`); 76 | return; 77 | } 78 | const fullUrl = `https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&ids=${wikidataID}&props=descriptions&languages=en`; 79 | return fetch(fullUrl, { 80 | method: 'GET', 81 | headers: new Headers({ 82 | 'Api-User-Agent': 'wit-ai-example' 83 | }) 84 | }) 85 | .then(response => Promise.resolve(response.json())) 86 | .then(data => { 87 | console.log(`ooo yes I know ${celebrity.name} -- ${data.entities[wikidataID].descriptions.en.value}`); 88 | }) 89 | .catch(err => console.error(err)) 90 | }; 91 | 92 | const client = new Wit({accessToken}); 93 | interactive(client, handleMessage); 94 | -------------------------------------------------------------------------------- /examples/joke.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | let Wit = null; 8 | let interactive = null; 9 | try { 10 | // if running from repo 11 | Wit = require('../').Wit; 12 | interactive = require('../').interactive; 13 | } catch (e) { 14 | Wit = require('node-wit').Wit; 15 | interactive = require('node-wit').interactive; 16 | } 17 | 18 | const accessToken = (() => { 19 | if (process.argv.length !== 3) { 20 | console.log('usage: node examples/joke.js '); 21 | console.log('Please import the corresponding .zip file upon creating an app and grab your access token from the Settings page'); 22 | process.exit(1); 23 | } 24 | return process.argv[2]; 25 | })(); 26 | 27 | // Joke example 28 | // See https://wit.ai/aleka/wit-example-joke-bot/ 29 | 30 | const allJokes = { 31 | chuck: [ 32 | 'Chuck Norris counted to infinity - twice.', 33 | 'Death once had a near-Chuck Norris experience.', 34 | ], 35 | tech: [ 36 | 'Did you hear about the two antennas that got married? The ceremony was long and boring, but the reception was great!', 37 | 'Why do geeks mistake Halloween and Christmas? Because Oct 31 === Dec 25.', 38 | ], 39 | default: [ 40 | 'Why was the Math book sad? Because it had so many problems.', 41 | ], 42 | }; 43 | 44 | const firstValue = (obj, key) => { 45 | const val = obj && obj[key] && 46 | Array.isArray(obj[key]) && 47 | obj[key].length > 0 && 48 | obj[key][0].value 49 | ; 50 | if (!val) { 51 | return null; 52 | } 53 | return val; 54 | }; 55 | 56 | const handleMessage = ({entities, traits}) => { 57 | const getJoke = firstValue(traits, 'getJoke'); 58 | const greetings = firstValue(traits, 'wit$greetings'); 59 | const category = firstValue(entities, 'category:category'); 60 | const sentiment = firstValue(traits, 'wit$sentiment'); 61 | if (getJoke) { 62 | if (category) { 63 | const jokes = allJokes[category]; 64 | console.log(jokes[Math.floor(Math.random() * jokes.length)]); 65 | } else { 66 | console.log(allJokes['default'][0]); 67 | } 68 | } else if (sentiment) { 69 | const reply = sentiment === 'positive' ? 'Glad you liked it.' : 'Hmm.'; 70 | console.log(reply); 71 | } else if (greetings) { 72 | console.log("hey this is joke bot :)"); 73 | } else { 74 | console.log("I can tell jokes! Say 'tell me a joke about tech'!"); 75 | } 76 | }; 77 | 78 | const client = new Wit({accessToken}); 79 | interactive(client, handleMessage); 80 | -------------------------------------------------------------------------------- /examples/messenger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // Messenger API integration example 8 | // We assume you have: 9 | // * a Wit.ai bot setup (https://wit.ai/docs/quickstart) 10 | // * a Messenger Platform setup (https://developers.facebook.com/docs/messenger-platform/quickstart) 11 | // You need to `npm install` the following dependencies: body-parser, express, node-fetch. 12 | // 13 | // 1. npm install body-parser express node-fetch 14 | // 2. Download and install ngrok from https://ngrok.com/download 15 | // 3. ./ngrok http 8445 16 | // 4. WIT_TOKEN=your_access_token FB_APP_SECRET=your_app_secret FB_PAGE_TOKEN=your_page_token node examples/messenger.js 17 | // 5. Subscribe your page to the Webhooks using verify_token and `https:///webhook` as callback URL. 18 | // 6. Talk to your bot on Messenger! 19 | 20 | const bodyParser = require('body-parser'); 21 | const crypto = require('crypto'); 22 | const express = require('express'); 23 | const fetch = require('node-fetch'); 24 | 25 | let Wit = null; 26 | let log = null; 27 | try { 28 | // if running from repo 29 | Wit = require('../').Wit; 30 | log = require('../').log; 31 | } catch (e) { 32 | Wit = require('node-wit').Wit; 33 | log = require('node-wit').log; 34 | } 35 | 36 | // Webserver parameter 37 | const PORT = process.env.PORT || 8445; 38 | 39 | // Wit.ai parameters 40 | const WIT_TOKEN = process.env.WIT_TOKEN; 41 | 42 | // Messenger API parameters 43 | const FB_PAGE_TOKEN = process.env.FB_PAGE_TOKEN; 44 | if (!FB_PAGE_TOKEN) { throw new Error('missing FB_PAGE_TOKEN') } 45 | const FB_APP_SECRET = process.env.FB_APP_SECRET; 46 | if (!FB_APP_SECRET) { throw new Error('missing FB_APP_SECRET') } 47 | 48 | let FB_VERIFY_TOKEN = null; 49 | crypto.randomBytes(8, (err, buff) => { 50 | if (err) throw err; 51 | FB_VERIFY_TOKEN = buff.toString('hex'); 52 | console.log(`/webhook will accept the Verify Token "${FB_VERIFY_TOKEN}"`); 53 | }); 54 | 55 | // ---------------------------------------------------------------------------- 56 | // Messenger API specific code 57 | 58 | // See the Send API reference 59 | // https://developers.facebook.com/docs/messenger-platform/send-api-reference 60 | 61 | const fbMessage = (id, text) => { 62 | const body = JSON.stringify({ 63 | recipient: { id }, 64 | message: { text }, 65 | }); 66 | const qs = 'access_token=' + encodeURIComponent(FB_PAGE_TOKEN); 67 | return fetch('https://graph.facebook.com/me/messages?' + qs, { 68 | method: 'POST', 69 | headers: {'Content-Type': 'application/json'}, 70 | body, 71 | }) 72 | .then(rsp => rsp.json()) 73 | .then(json => { 74 | if (json.error && json.error.message) { 75 | throw new Error(json.error.message); 76 | } 77 | return json; 78 | }); 79 | }; 80 | 81 | // ---------------------------------------------------------------------------- 82 | // Wit.ai bot specific code 83 | 84 | // This will contain all user sessions. 85 | // Each session has an entry: 86 | // sessionId -> {fbid: facebookUserId, context: sessionState} 87 | const sessions = {}; 88 | 89 | const findOrCreateSession = (fbid) => { 90 | let sessionId; 91 | // Let's see if we already have a session for the user fbid 92 | Object.keys(sessions).forEach(k => { 93 | if (sessions[k].fbid === fbid) { 94 | // Yep, got it! 95 | sessionId = k; 96 | } 97 | }); 98 | if (!sessionId) { 99 | // No session found for user fbid, let's create a new one 100 | sessionId = new Date().toISOString(); 101 | sessions[sessionId] = {fbid: fbid, context: {}}; 102 | } 103 | return sessionId; 104 | }; 105 | 106 | // Setting up our bot 107 | const wit = new Wit({ 108 | accessToken: WIT_TOKEN, 109 | logger: new log.Logger(log.INFO) 110 | }); 111 | 112 | // Starting our webserver and putting it all together 113 | const app = express(); 114 | app.use(({method, url}, rsp, next) => { 115 | rsp.on('finish', () => { 116 | console.log(`${rsp.statusCode} ${method} ${url}`); 117 | }); 118 | next(); 119 | }); 120 | app.use(bodyParser.json({ verify: verifyRequestSignature })); 121 | 122 | // Webhook setup 123 | app.get('/webhook', (req, res) => { 124 | if (req.query['hub.mode'] === 'subscribe' && 125 | req.query['hub.verify_token'] === FB_VERIFY_TOKEN) { 126 | res.send(req.query['hub.challenge']); 127 | } else { 128 | res.sendStatus(400); 129 | } 130 | }); 131 | 132 | // Message handler 133 | app.post('/webhook', (req, res) => { 134 | // Parse the Messenger payload 135 | // See the Webhook reference 136 | // https://developers.facebook.com/docs/messenger-platform/webhook-reference 137 | const data = req.body; 138 | 139 | if (data.object === 'page') { 140 | data.entry.forEach(entry => { 141 | entry.messaging.forEach(event => { 142 | if (event.message && !event.message.is_echo) { 143 | // Yay! We got a new message! 144 | // We retrieve the Facebook user ID of the sender 145 | const sender = event.sender.id; 146 | 147 | // We could retrieve the user's current session, or create one if it doesn't exist 148 | // This is useful if we want our bot to figure out the conversation history 149 | // const sessionId = findOrCreateSession(sender); 150 | 151 | // We retrieve the message content 152 | const {text, attachments} = event.message; 153 | 154 | if (attachments) { 155 | // We received an attachment 156 | // Let's reply with an automatic message 157 | fbMessage(sender, 'Sorry I can only process text messages for now.') 158 | .catch(console.error); 159 | } else if (text) { 160 | // We received a text message 161 | // Let's run /message on the text to extract some entities, intents and traits 162 | wit.message(text).then(({entities, intents, traits}) => { 163 | // You can customize your response using these 164 | console.log(intents); 165 | console.log(entities); 166 | console.log(traits); 167 | // For now, let's reply with another automatic message 168 | fbMessage(sender, `We've received your message: ${text}.`); 169 | }) 170 | .catch((err) => { 171 | console.error('Oops! Got an error from Wit: ', err.stack || err); 172 | }) 173 | } 174 | } else { 175 | console.log('received event', JSON.stringify(event)); 176 | } 177 | }); 178 | }); 179 | } 180 | res.sendStatus(200); 181 | }); 182 | 183 | /* 184 | * Verify that the callback came from Facebook. Using the App Secret from 185 | * the App Dashboard, we can verify the signature that is sent with each 186 | * callback in the x-hub-signature field, located in the header. 187 | * 188 | * https://developers.facebook.com/docs/graph-api/webhooks#setup 189 | * 190 | */ 191 | function verifyRequestSignature(req, res, buf) { 192 | var signature = req.headers["x-hub-signature"]; 193 | console.log(signature); 194 | 195 | if (!signature) { 196 | // For testing, let's log an error. In production, you should throw an 197 | // error. 198 | console.error("Couldn't validate the signature."); 199 | } else { 200 | var elements = signature.split('='); 201 | var method = elements[0]; 202 | var signatureHash = elements[1]; 203 | 204 | var expectedHash = crypto.createHmac('sha1', FB_APP_SECRET) 205 | .update(buf) 206 | .digest('hex'); 207 | 208 | if (signatureHash != expectedHash) { 209 | throw new Error("Couldn't validate the request signature."); 210 | } 211 | } 212 | } 213 | 214 | app.listen(PORT); 215 | console.log('Listening on :' + PORT + '...'); 216 | -------------------------------------------------------------------------------- /examples/pizza.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | let Wit = null; 8 | let interactive = null; 9 | try { 10 | // if running from repo 11 | Wit = require('../').Wit; 12 | interactive = require('../').interactive; 13 | } catch (e) { 14 | Wit = require('node-wit').Wit; 15 | interactive = require('node-wit').interactive; 16 | } 17 | 18 | const accessToken = (() => { 19 | if (process.argv.length !== 3) { 20 | console.log('usage: node examples/pizza.js '); 21 | process.exit(1); 22 | } 23 | return process.argv[2]; 24 | })(); 25 | 26 | const actions = { 27 | process_order(contextMap) { 28 | const {order} = contextMap; 29 | if (typeof order !== 'object') { 30 | console.log('could not find order'); 31 | return {context_map: contextMap}; 32 | } 33 | 34 | const pizze = Array.from(order.pizze || []); 35 | const pizze_number = pizze.length; 36 | if (pizze_number < 1) { 37 | console.log('could not find any pizze in the order'); 38 | return {context_map: contextMap}; 39 | } 40 | 41 | const processed = pizze.length; 42 | const order_number = pizze[0].type.substring(0, 3).toUpperCase() + '-42X6'; 43 | 44 | return {context_map: {...contextMap, pizze_number, order_number}}; 45 | }, 46 | make_summary(contextMap) { 47 | const {order} = contextMap; 48 | if (typeof order !== 'object') { 49 | console.log('could not find order'); 50 | return {context_map: contextMap}; 51 | } 52 | 53 | const pizze = Array.from(order.pizze || []); 54 | if (pizze.length < 1) { 55 | console.log('could not find any pizze in the order'); 56 | return {context_map: contextMap}; 57 | } 58 | 59 | const order_summary = pizze 60 | .map(({size, type}) => 'a ' + size + ' ' + type) 61 | .join(', '); 62 | 63 | return {context_map: {...contextMap, order_summary}, stop: true}; 64 | }, 65 | }; 66 | 67 | const client = new Wit({accessToken, actions}); 68 | interactive(client); 69 | -------------------------------------------------------------------------------- /examples/synthesize-speech.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const fs = require('fs'); 8 | const mic = require('mic'); 9 | const readline = require('readline'); 10 | 11 | const AUDIO_PATH = '/tmp/output.raw'; 12 | const VOICE_PATH = __dirname + '/audio.wav'; 13 | const MIC_TIMEOUT_MS = 10000; 14 | const VOICE = 'Charlie'; 15 | 16 | let Wit = null; 17 | let interactive = null; 18 | try { 19 | // if running from repo 20 | Wit = require('../').Wit; 21 | interactive = require('../').interactive; 22 | } catch (e) { 23 | Wit = require('node-wit').Wit; 24 | interactive = require('node-wit').interactive; 25 | } 26 | 27 | const accessToken = (() => { 28 | if (process.argv.length !== 3) { 29 | console.log('usage: node examples/synthesize-speech.js '); 30 | process.exit(1); 31 | } 32 | return process.argv[2]; 33 | })(); 34 | 35 | const wit = new Wit({accessToken}); 36 | 37 | const rl = readline.createInterface({ 38 | input: process.stdin, 39 | output: process.stdout, 40 | }); 41 | rl.setPrompt('Enter a phrase, or type `!s` to start recording > '); 42 | 43 | var fullTranscript; 44 | 45 | const prompt = () => { 46 | rl.prompt(); 47 | rl.write(null, {ctrl: true, name: 'e'}); 48 | fullTranscript = ''; 49 | }; 50 | prompt(); 51 | 52 | const handleTranscription = rsp => { 53 | console.log(`Synthesizing text: ${fullTranscript}`); 54 | 55 | wit.synthesize(fullTranscript, VOICE) 56 | .then(saveVoice) 57 | .catch(console.error); 58 | }; 59 | 60 | const saveVoice = rsp => { 61 | rsp.body.pipe(fs.createWriteStream(VOICE_PATH)); 62 | 63 | console.log("Saved audio to " + VOICE_PATH); 64 | 65 | prompt(); 66 | } 67 | 68 | wit.on('partialTranscription', text => { 69 | console.log(text + '...'); 70 | }); 71 | wit.on('fullTranscription', text => { 72 | console.log(text + ' (final)'); 73 | 74 | fullTranscript += `${text} ` 75 | }); 76 | 77 | rl.on('line', line => { 78 | line = line.trim(); 79 | if (!line) { 80 | return prompt(); 81 | } 82 | 83 | // POST /dictation 84 | if (line === '!s') { 85 | const microphone = mic({ 86 | bitwidth: '16', 87 | channels: '1', 88 | encoding: 'signed-integer', 89 | endian: 'little', 90 | fileType: 'raw', 91 | rate: '16000', 92 | }); 93 | 94 | const inputAudioStream = microphone.getAudioStream(); 95 | const outputFileStream = fs.WriteStream(AUDIO_PATH); 96 | inputAudioStream.pipe(outputFileStream); 97 | 98 | inputAudioStream.on('startComplete', () => { 99 | setTimeout(() => { 100 | microphone.stop(); 101 | }, MIC_TIMEOUT_MS); 102 | }); 103 | inputAudioStream.on('stopComplete', () => { 104 | const stream = fs.ReadStream(AUDIO_PATH); 105 | wit 106 | .dictation( 107 | 'audio/raw;encoding=signed-integer;bits=16;rate=16000;endian=little', 108 | stream, 109 | ) 110 | .then(handleTranscription) 111 | .catch(console.error); 112 | }); 113 | 114 | microphone.start(); 115 | console.log('🎤 Listening...'); 116 | 117 | return; 118 | } 119 | 120 | // POST /synthesize 121 | wit.synthesize(line, VOICE) 122 | .then(saveVoice) 123 | .catch(console.error); 124 | }); 125 | -------------------------------------------------------------------------------- /examples/wit-example-celebrities.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wit-ai/node-wit/8a5cfcbf5baf4d27ffca7e7e4d67556af708ef6c/examples/wit-example-celebrities.zip -------------------------------------------------------------------------------- /examples/wit-example-joke-bot.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wit-ai/node-wit/8a5cfcbf5baf4d27ffca7e7e4d67556af708ef6c/examples/wit-example-joke-bot.zip -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | module.exports = { 6 | log: require('./lib/log'), 7 | Wit: require('./lib/wit'), 8 | interactive: require('./lib/interactive') 9 | }; 10 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | module.exports = { 6 | DEFAULT_API_VERSION: 20230215, 7 | DEFAULT_WIT_URL: 'https://api.wit.ai', 8 | }; 9 | -------------------------------------------------------------------------------- /lib/interactive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const fs = require('fs'); 8 | const mic = require('mic'); 9 | const readline = require('readline'); 10 | const { v4: uuidv4 } = require('uuid'); 11 | 12 | const sessionId = uuidv4(); 13 | 14 | const AUDIO_PATH = '/tmp/output.raw'; 15 | const MIC_TIMEOUT_MS = 3000; 16 | const MSG_PREFIX_COMMAND = '!message'; 17 | 18 | module.exports = (wit, handleResponse, initContextMap) => { 19 | let contextMap = typeof initContextMap === 'object' ? initContextMap : {}; 20 | 21 | const rl = readline.createInterface({ 22 | input: process.stdin, 23 | output: process.stdout, 24 | }); 25 | rl.setPrompt('> '); 26 | 27 | const prompt = () => { 28 | rl.prompt(); 29 | rl.write(null, {ctrl: true, name: 'e'}); 30 | }; 31 | prompt(); 32 | 33 | const makeResponseHandler = rsp => { 34 | const {context_map} = rsp; 35 | if (typeof context_map === 'object') { 36 | contextMap = context_map; 37 | } 38 | 39 | if (handleResponse) { 40 | handleResponse(rsp); 41 | } else { 42 | console.log(JSON.stringify(rsp)); 43 | } 44 | 45 | return rsp; 46 | }; 47 | 48 | const openMic = onComplete => { 49 | const microphone = mic({ 50 | bitwidth: '16', 51 | channels: '1', 52 | encoding: 'signed-integer', 53 | endian: 'little', 54 | fileType: 'raw', 55 | rate: '16000', 56 | }); 57 | 58 | const inputAudioStream = microphone.getAudioStream(); 59 | const outputFileStream = fs.WriteStream(AUDIO_PATH); 60 | inputAudioStream.pipe(outputFileStream); 61 | 62 | inputAudioStream.on('startComplete', () => { 63 | setTimeout(() => { 64 | microphone.stop(); 65 | }, MIC_TIMEOUT_MS); 66 | }); 67 | inputAudioStream.on('stopComplete', () => onComplete()); 68 | 69 | microphone.start(); 70 | console.log('🎤 Listening...'); 71 | }; 72 | 73 | wit.on('partialTranscription', text => { 74 | console.log(text + '...'); 75 | }); 76 | wit.on('fullTranscription', text => { 77 | console.log(text + ' (final)'); 78 | }); 79 | wit.on('partialUnderstanding', rsp => { 80 | console.log('Live understanding: ' + JSON.stringify(rsp)); 81 | }); 82 | wit.on('response', ({text}) => { 83 | console.log('< ' + text); 84 | }); 85 | 86 | rl.on('line', line => { 87 | line = line.trim(); 88 | if (!line) { 89 | return prompt(); 90 | } 91 | 92 | // POST /converse 93 | if (line === '!converse') { 94 | const onComplete = () => { 95 | const stream = fs.ReadStream(AUDIO_PATH); 96 | wit 97 | .runComposerAudio( 98 | sessionId, 99 | 'audio/raw;encoding=signed-integer;bits=16;rate=16000;endian=little', 100 | stream, 101 | contextMap, 102 | ) 103 | .then(makeResponseHandler) 104 | .then(({expects_input}) => { 105 | if (expects_input) { 106 | openMic(onComplete); 107 | } else { 108 | prompt(); 109 | } 110 | }) 111 | .catch(console.error); 112 | }; 113 | 114 | return openMic(onComplete); 115 | } 116 | 117 | // POST /speech 118 | if (line === '!speech') { 119 | const onComplete = () => { 120 | const stream = fs.ReadStream(AUDIO_PATH); 121 | wit 122 | .speech( 123 | 'audio/raw;encoding=signed-integer;bits=16;rate=16000;endian=little', 124 | stream, 125 | ) 126 | .then(makeResponseHandler) 127 | .then(prompt) 128 | .catch(console.error); 129 | }; 130 | return openMic(onComplete); 131 | } 132 | 133 | if (line.startsWith(MSG_PREFIX_COMMAND)) { 134 | // GET /message 135 | return wit 136 | .message(line.slice(MSG_PREFIX_COMMAND.length)) 137 | .then(makeResponseHandler) 138 | .then(prompt) 139 | .catch(console.error); 140 | } 141 | 142 | // POST /event 143 | wit 144 | .runComposer(sessionId, contextMap, line) 145 | .then(makeResponseHandler) 146 | .then(prompt) 147 | .catch(console.error); 148 | }); 149 | }; 150 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const DEBUG = 'debug'; 8 | const INFO = 'info'; 9 | const WARN = 'warn'; 10 | const ERROR = 'error'; 11 | 12 | const levels = [DEBUG, INFO, WARN, ERROR]; 13 | const funcs = { 14 | [DEBUG]: console.debug.bind(console, '[wit][debug]'), 15 | [INFO]: console.log.bind(console, '[wit]'), 16 | [WARN]: console.warn.bind(console, '[wit]'), 17 | [ERROR]: console.error.bind(console, '[wit]'), 18 | }; 19 | const noop = () => {} 20 | 21 | const Logger = function(lvl) { 22 | this.level = lvl || INFO; 23 | 24 | levels.forEach((x) => { 25 | const should = levels.indexOf(x) >= levels.indexOf(lvl); 26 | this[x] = should ? funcs[x] : noop; 27 | }); 28 | }; 29 | 30 | module.exports = { Logger, DEBUG, INFO, WARN, ERROR }; 31 | -------------------------------------------------------------------------------- /lib/wit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const {DEFAULT_API_VERSION, DEFAULT_WIT_URL} = require('./config'); 8 | const log = require('./log'); 9 | const fetch = require('isomorphic-fetch'); 10 | const EventEmitter = require('events'); 11 | const HttpsProxyAgent = require('https-proxy-agent'); 12 | const {Readable} = require('stream'); 13 | const Url = require('url'); 14 | 15 | const LIVE_UNDERSTANDING_API_VERSION = 20220608; 16 | const MULTI_RESPONSES_API_VERSION = 20230215; 17 | 18 | class Wit extends EventEmitter { 19 | constructor(opts) { 20 | super(); 21 | this.config = Object.freeze(validate(opts)); 22 | } 23 | 24 | runComposer(sessionId, contextMap, message) { 25 | return this.event(sessionId, contextMap, message).then( 26 | this.makeComposerHandler(sessionId), 27 | ); 28 | } 29 | 30 | runComposerAudio(sessionId, contentType, body, contextMap) { 31 | return this.converse(sessionId, contentType, body, contextMap).then( 32 | this.makeComposerHandler(sessionId), 33 | ); 34 | } 35 | 36 | converse(sessionId, contentType, body, contextMap) { 37 | if (typeof sessionId !== 'string') { 38 | throw new Error('Please provide a session ID (string).'); 39 | } 40 | 41 | if (typeof contentType !== 'string') { 42 | throw new Error('Please provide a content-type (string).'); 43 | } 44 | 45 | if (!body instanceof Readable) { 46 | throw new Error('Please provide an audio stream (Readable).'); 47 | } 48 | 49 | const {actions, apiVersion, headers, logger, proxy, witURL} = this.config; 50 | 51 | const params = { 52 | session_id: sessionId, 53 | v: apiVersion, 54 | }; 55 | 56 | if (typeof contextMap === 'object') { 57 | params.context_map = JSON.stringify(contextMap); 58 | } 59 | 60 | const method = 'POST'; 61 | const fullURL = witURL + '/converse?' + encodeURIParams(params); 62 | logger.debug(method, fullURL); 63 | 64 | const multi_responses_enabled = apiVersion >= MULTI_RESPONSES_API_VERSION; 65 | 66 | const req = fetch(fullURL, { 67 | body, 68 | method, 69 | proxy, 70 | headers: { 71 | ...headers, 72 | 'Content-Type': contentType, 73 | 'Transfer-Encoding': 'chunked', 74 | }, 75 | }); 76 | 77 | const _partialResponses = req 78 | .then( 79 | resp => 80 | new Promise((resolve, reject) => { 81 | logger.debug('status', resp.status); 82 | const bodyStream = resp.body; 83 | 84 | bodyStream.on('readable', () => { 85 | let chunk; 86 | let contents = ''; 87 | while (null !== (chunk = bodyStream.read())) { 88 | contents += chunk.toString(); 89 | } 90 | 91 | for (const rsp of parseResponse(contents)) { 92 | const {action, context_map, error, is_final, response, text} = 93 | rsp; 94 | 95 | // Live transcription 96 | if (!(error || is_final)) { 97 | logger.debug('[converse] partialTranscription:', text); 98 | this.emit('partialTranscription', text); 99 | } 100 | 101 | // Multi-responses 102 | if ( 103 | multi_responses_enabled && 104 | !(error || is_final) && 105 | (action || response) 106 | ) { 107 | if (response) { 108 | logger.debug('[converse] partialResponse:', response); 109 | this.emit('response', response); 110 | } 111 | 112 | if (action) { 113 | logger.debug('[converse] got partial action:', action); 114 | runAction(logger, actions, action, context_map); 115 | } 116 | } 117 | } 118 | }); 119 | }), 120 | ) 121 | .catch(e => 122 | logger.error('[converse] could not parse partial response', e), 123 | ); 124 | 125 | return req 126 | .then(response => Promise.all([response.text(), response.status])) 127 | .then(([contents, status]) => { 128 | const finalResponse = parseResponse(contents).pop(); 129 | const {text} = finalResponse; 130 | 131 | logger.debug('[converse] fullTranscription:', text); 132 | this.emit('fullTranscription', text); 133 | 134 | return [finalResponse, status]; 135 | }) 136 | .catch(e => e) 137 | .then(makeWitResponseHandler(logger, 'converse')); 138 | } 139 | 140 | event(sessionId, contextMap, message) { 141 | if (typeof sessionId !== 'string') { 142 | throw new Error('Please provide a session ID (string).'); 143 | } 144 | 145 | const {actions, apiVersion, headers, logger, proxy, witURL} = this.config; 146 | 147 | const params = { 148 | session_id: sessionId, 149 | v: apiVersion, 150 | }; 151 | 152 | if (typeof contextMap === 'object') { 153 | params.context_map = JSON.stringify(contextMap); 154 | } 155 | 156 | const body = {}; 157 | if (typeof message === 'string') { 158 | body.type = 'message'; 159 | body.message = message; 160 | } 161 | 162 | const method = 'POST'; 163 | const fullURL = witURL + '/event?' + encodeURIParams(params); 164 | logger.debug(method, fullURL); 165 | 166 | const req = fetch(fullURL, { 167 | body: JSON.stringify(body), 168 | method, 169 | headers, 170 | proxy, 171 | }); 172 | 173 | // Multi-responses 174 | if (apiVersion >= MULTI_RESPONSES_API_VERSION) { 175 | const _partialResponses = req 176 | .then( 177 | resp => 178 | new Promise((resolve, reject) => { 179 | logger.debug('status', resp.status); 180 | const bodyStream = resp.body; 181 | 182 | bodyStream.on('readable', () => { 183 | let chunk; 184 | let contents = ''; 185 | while (null !== (chunk = bodyStream.read())) { 186 | contents += chunk.toString(); 187 | } 188 | 189 | for (const rsp of parseResponse(contents)) { 190 | const {action, context_map, error, is_final, response} = rsp; 191 | 192 | if (!(error || is_final) && (action || response)) { 193 | if (response) { 194 | logger.debug('[event] partialResponse:', response); 195 | this.emit('response', response); 196 | } 197 | 198 | if (action) { 199 | logger.debug('[event] got partial action:', action); 200 | runAction(logger, actions, action, context_map); 201 | } 202 | } 203 | } 204 | }); 205 | }), 206 | ) 207 | .catch(e => 208 | logger.error('[event] could not parse partial response', e), 209 | ); 210 | } 211 | 212 | return req 213 | .then(response => Promise.all([response.text(), response.status])) 214 | .then(([contents, status]) => ([parseResponse(contents).pop(), status])) 215 | .catch(e => e) 216 | .then(makeWitResponseHandler(logger, 'event')); 217 | } 218 | 219 | message(q, context, n) { 220 | if (typeof q !== 'string') { 221 | throw new Error('Please provide a text input (string).'); 222 | } 223 | 224 | const {apiVersion, headers, logger, proxy, witURL} = this.config; 225 | 226 | const params = { 227 | q, 228 | v: apiVersion, 229 | }; 230 | 231 | if (typeof context === 'object') { 232 | params.context = JSON.stringify(context); 233 | } 234 | 235 | if (typeof n === 'number') { 236 | params.n = JSON.stringify(n); 237 | } 238 | 239 | const method = 'GET'; 240 | const fullURL = witURL + '/message?' + encodeURIParams(params); 241 | logger.debug(method, fullURL); 242 | 243 | return fetch(fullURL, { 244 | method, 245 | headers, 246 | proxy, 247 | }) 248 | .then(response => Promise.all([response.json(), response.status])) 249 | .then(makeWitResponseHandler(logger, 'message')); 250 | } 251 | 252 | speech(contentType, body, context, n) { 253 | if (typeof contentType !== 'string') { 254 | throw new Error('Please provide a content-type (string).'); 255 | } 256 | 257 | if (!body instanceof Readable) { 258 | throw new Error('Please provide an audio stream (Readable).'); 259 | } 260 | 261 | const {apiVersion, headers, logger, proxy, witURL} = this.config; 262 | 263 | const params = { 264 | v: apiVersion, 265 | }; 266 | 267 | if (typeof context === 'object') { 268 | params.context = JSON.stringify(context); 269 | } 270 | 271 | if (typeof n === 'number') { 272 | params.n = JSON.stringify(n); 273 | } 274 | 275 | const method = 'POST'; 276 | const fullURL = witURL + '/speech?' + encodeURIParams(params); 277 | logger.debug(method, fullURL); 278 | 279 | const live_understanding_enabled = 280 | apiVersion >= LIVE_UNDERSTANDING_API_VERSION; 281 | 282 | const req = fetch(fullURL, { 283 | body, 284 | method, 285 | agent: proxy, 286 | headers: { 287 | ...headers, 288 | 'Content-Type': contentType, 289 | 'Transfer-Encoding': 'chunked', 290 | }, 291 | }); 292 | 293 | const _partialResponses = req 294 | .then( 295 | response => 296 | new Promise((resolve, reject) => { 297 | logger.debug('status', response.status); 298 | const bodyStream = response.body; 299 | 300 | bodyStream.on('readable', () => { 301 | let chunk; 302 | let contents = ''; 303 | while (null !== (chunk = bodyStream.read())) { 304 | contents += chunk.toString(); 305 | } 306 | 307 | for (const rsp of parseResponse(contents)) { 308 | const {error, intents, is_final, text} = rsp; 309 | 310 | // Live transcription 311 | if (!(error || intents)) { 312 | logger.debug('[speech] partialTranscription:', text); 313 | this.emit('partialTranscription', text); 314 | } 315 | 316 | // Live understanding 317 | if (live_understanding_enabled && intents && !is_final) { 318 | logger.debug('[speech] partialUnderstanding:', rsp); 319 | this.emit('partialUnderstanding', rsp); 320 | } 321 | } 322 | }); 323 | }), 324 | ) 325 | .catch(e => logger.error('[speech] could not parse partial response', e)); 326 | 327 | return req 328 | .then(response => Promise.all([response.text(), response.status])) 329 | .then(([contents, status]) => { 330 | const finalResponse = parseResponse(contents).pop(); 331 | const {text} = finalResponse; 332 | 333 | logger.debug('[speech] fullTranscription:', text); 334 | this.emit('fullTranscription', text); 335 | 336 | return [finalResponse, status]; 337 | }) 338 | .catch(e => e) 339 | .then(makeWitResponseHandler(logger, 'speech')); 340 | } 341 | 342 | dictation(contentType, body) { 343 | if (typeof contentType !== 'string') { 344 | throw new Error('Please provide a content-type (string).'); 345 | } 346 | 347 | if (!body instanceof Readable) { 348 | throw new Error('Please provide an audio stream (Readable).'); 349 | } 350 | 351 | const {apiVersion, headers, logger, proxy, witURL} = this.config; 352 | 353 | const params = { 354 | v: apiVersion, 355 | }; 356 | 357 | const method = 'POST'; 358 | const fullURL = witURL + '/dictation?' + encodeURIParams(params); 359 | logger.debug(method, fullURL); 360 | 361 | const req = fetch(fullURL, { 362 | body, 363 | method, 364 | proxy, 365 | headers: { 366 | ...headers, 367 | 'Content-Type': contentType, 368 | 'Transfer-Encoding': 'chunked', 369 | }, 370 | }); 371 | 372 | const _partialResponses = req 373 | .then( 374 | response => 375 | new Promise((resolve, reject) => { 376 | logger.debug('status', response.status); 377 | const bodyStream = response.body; 378 | bodyStream.on('readable', () => { 379 | let chunk; 380 | let contents = ''; 381 | while (null !== (chunk = bodyStream.read())) { 382 | contents += chunk.toString(); 383 | } 384 | 385 | for (const rsp of parseResponse(contents)) { 386 | const {error, is_final, text} = rsp; 387 | 388 | // Live transcription 389 | if (!error) { 390 | if (!is_final) { 391 | logger.debug('[dictation] partial transcription:', text); 392 | this.emit('partialTranscription', text); 393 | } else { 394 | logger.debug( 395 | '[dictation] full sentence transcription:', 396 | text, 397 | ); 398 | this.emit('fullTranscription', text); 399 | } 400 | } 401 | } 402 | }); 403 | }), 404 | ) 405 | .catch(e => 406 | logger.error('[dictation] could not parse partial response', e), 407 | ); 408 | 409 | return req 410 | .then(response => Promise.all([response.text(), response.status])) 411 | .then(([contents, status]) => { 412 | const finalResponse = parseResponse(contents).pop(); 413 | const {text} = finalResponse; 414 | 415 | logger.debug('[dictation] last full sentence transcription:', text); 416 | this.emit('fullTranscription', text); 417 | 418 | return [finalResponse, status]; 419 | }) 420 | .catch(e => e) 421 | .then(makeWitResponseHandler(logger, 'dictation')); 422 | } 423 | 424 | synthesize( 425 | q, 426 | voice, 427 | style = 'default', 428 | speed = 100, 429 | pitch = 100, 430 | gain = 100, 431 | ) { 432 | if (typeof q !== 'string') { 433 | throw new Error('Please provide a text input (string).'); 434 | } 435 | if (typeof voice !== 'string') { 436 | throw new Error('Please provide a voice input (string).'); 437 | } 438 | 439 | const {apiVersion, headers, logger, proxy, witURL} = this.config; 440 | 441 | const params = { 442 | v: apiVersion, 443 | }; 444 | 445 | const body = { 446 | q: q, 447 | voice: voice, 448 | style: style, 449 | speed: speed, 450 | pitch: pitch, 451 | gain: gain, 452 | }; 453 | 454 | const method = 'POST'; 455 | const fullURL = witURL + '/synthesize?' + encodeURIParams(params); 456 | logger.debug(method, fullURL); 457 | 458 | return fetch(fullURL, { 459 | body: JSON.stringify(body), 460 | method, 461 | proxy, 462 | headers: { 463 | ...headers, 464 | 'Content-Type': 'application/json', 465 | }, 466 | }) 467 | .then(response => Promise.all([response, response.status])) 468 | .then(makeWitResponseHandler(logger, 'synthesize')); 469 | } 470 | 471 | makeComposerHandler(sessionId) { 472 | const {actions, logger} = this.config; 473 | 474 | return ({context_map, action, expects_input, response}) => { 475 | if (typeof context_map !== 'object') { 476 | throw new Error( 477 | 'Unexpected context_map in API response: ' + 478 | JSON.stringify(context_map), 479 | ); 480 | } 481 | 482 | if (response) { 483 | logger.debug('[composer] response:', response); 484 | this.emit('response', response); 485 | } 486 | 487 | if (action) { 488 | logger.debug('[composer] got action', action); 489 | return runAction(logger, actions, action, context_map).then( 490 | ({context_map, stop}) => { 491 | if (expects_input && !stop) { 492 | return this.runComposer(sessionId, context_map); 493 | } 494 | return {context_map}; 495 | }, 496 | ); 497 | } 498 | 499 | return {context_map, expects_input}; 500 | }; 501 | } 502 | } 503 | 504 | const runAction = (logger, actions, name, ...rest) => { 505 | logger.debug('Running action', name); 506 | return Promise.resolve(actions[name](...rest)); 507 | }; 508 | 509 | const makeWitResponseHandler = (logger, endpoint) => rsp => { 510 | const error = e => { 511 | logger.error('[' + endpoint + '] Error: ' + e); 512 | throw e; 513 | }; 514 | 515 | if (rsp instanceof Error) { 516 | return error(rsp); 517 | } 518 | 519 | const [json, status] = rsp; 520 | 521 | if (json instanceof Error) { 522 | return error(json); 523 | } 524 | 525 | const err = json.error || (status !== 200 && json.body + ' (' + status + ')'); 526 | 527 | if (err) { 528 | return error(err); 529 | } 530 | 531 | logger.debug('[' + endpoint + '] Response: ' + JSON.stringify(json)); 532 | return json; 533 | }; 534 | 535 | const getProxyAgent = witURL => { 536 | const url = Url.parse(witURL); 537 | const proxy = 538 | url.protocol === 'http:' 539 | ? process.env.http_proxy || process.env.HTTP_PROXY 540 | : process.env.https_proxy || process.env.HTTPS_PROXY; 541 | const noProxy = process.env.no_proxy || process.env.NO_PROXY; 542 | 543 | const shouldIgnore = noProxy && noProxy.indexOf(url.hostname) > -1; 544 | if (proxy && !shouldIgnore) { 545 | return new HttpsProxyAgent(proxy); 546 | } 547 | 548 | if (!proxy) { 549 | return null; 550 | } 551 | }; 552 | 553 | const encodeURIParams = params => 554 | Object.entries(params) 555 | .map(([key, value]) => key + '=' + encodeURIComponent(value)) 556 | .join('&'); 557 | 558 | const parseResponse = response => { 559 | const chunks = response 560 | .split('\r\n') 561 | .map(x => x.trim()) 562 | .filter(x => x.length > 0); 563 | 564 | let prev = ''; 565 | let jsons = []; 566 | for (const chunk of chunks) { 567 | try { 568 | prev += chunk; 569 | jsons.push(JSON.parse(prev)); 570 | prev = ''; 571 | } catch (_e) {} 572 | } 573 | 574 | return jsons; 575 | }; 576 | 577 | const validate = opts => { 578 | if (!opts.accessToken) { 579 | throw new Error( 580 | 'Could not find access token, learn more at https://wit.ai/docs', 581 | ); 582 | } 583 | 584 | opts.witURL = opts.witURL || DEFAULT_WIT_URL; 585 | opts.apiVersion = opts.apiVersion || DEFAULT_API_VERSION; 586 | opts.headers = opts.headers || { 587 | Authorization: 'Bearer ' + opts.accessToken, 588 | 'Content-Type': 'application/json', 589 | }; 590 | opts.logger = opts.logger || new log.Logger(log.INFO); 591 | opts.proxy = getProxyAgent(opts.witURL); 592 | 593 | if (opts.actions && typeof opts.actions !== 'object') { 594 | throw new Error('Please provide actions mapping (string -> function).'); 595 | } 596 | 597 | return opts; 598 | }; 599 | 600 | module.exports = Wit; 601 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-wit", 3 | "version": "6.6.0", 4 | "description": "Wit.ai Node.js SDK", 5 | "keywords": [ 6 | "wit", 7 | "wit.ai", 8 | "bot", 9 | "botengine", 10 | "bots", 11 | "nlp", 12 | "automation", 13 | "composer" 14 | ], 15 | "main": "index.js", 16 | "scripts": { 17 | "test": "mocha --timeout 10000 ./tests/lib.js" 18 | }, 19 | "repository": "https://github.com/wit-ai/node-wit", 20 | "author": "The Wit Team ", 21 | "dependencies": { 22 | "body-parser": "^1.18.2", 23 | "express": "^4.16.2", 24 | "https-proxy-agent": "^5.0.0", 25 | "isomorphic-fetch": "^2.2.1", 26 | "lodash": "^4.17.14", 27 | "mic": "^2.1.2", 28 | "uuid": "^9.0.0" 29 | }, 30 | "engines": { 31 | "node": ">=6.17.1" 32 | }, 33 | "devDependencies": { 34 | "chai": "^3.5.0", 35 | "mocha": "^3.5.3", 36 | "sinon": "^1.17.6" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) Meta Platforms, Inc. and its affiliates. All rights reserved. 3 | 4 | set -ex 5 | 6 | mkdir -p dist 7 | cp package.json dist 8 | cp index.js dist 9 | cp -R lib dist/lib 10 | mocha ./tests/dist.js 11 | ( 12 | cd dist 13 | npm publish 14 | ) 15 | rm -rf dist 16 | -------------------------------------------------------------------------------- /tests/dist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | describe('dist', () => { 8 | require('./shared').runTests(require('../dist/index')); 9 | }); 10 | -------------------------------------------------------------------------------- /tests/lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | describe('lib', () => { 8 | require('./shared').runTests(require('../index')); 9 | }); 10 | -------------------------------------------------------------------------------- /tests/shared.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const expect = require('chai').expect; 8 | const sinon = require('sinon'); 9 | 10 | module.exports.runTests = wit => { 11 | const log = wit.log; 12 | const Wit = wit.Wit; 13 | const interactive = wit.interactive; 14 | 15 | describe('logger', () => { 16 | let loggerStub; 17 | 18 | it('tests log flags', () => { 19 | expect(log.DEBUG).to.be.equal('debug'); 20 | expect(log.INFO).to.be.equal('info'); 21 | expect(log.WARN).to.be.equal('warn'); 22 | expect(log.ERROR).to.be.equal('error'); 23 | }); 24 | 25 | it('tests logger (DEBUG)', () => { 26 | const logger = new log.Logger(log.DEBUG); 27 | loggerStub = sinon.stub(logger, 'info').returns(Promise.resolve()); 28 | logger.info('one', 'two', 'three'); 29 | expect(loggerStub.calledOnce).to.be.true; 30 | expect(loggerStub.thisValues[0].level).to.be.equal('debug'); 31 | expect(loggerStub.calledWith('one', 'two', 'three')).to.be.true; 32 | }); 33 | 34 | it('tests logger (INFO)', () => { 35 | const logger = new log.Logger(log.INFO); 36 | loggerStub = sinon.stub(logger, 'info').returns(Promise.resolve()); 37 | logger.info('one', 'two', 'three'); 38 | expect(loggerStub.calledOnce).to.be.true; 39 | expect(loggerStub.thisValues[0].level).to.be.equal('info'); 40 | expect(loggerStub.calledWith('one', 'two', 'three')).to.be.true; 41 | }); 42 | 43 | it('tests logger (WARN)', () => { 44 | const logger = new log.Logger(log.WARN); 45 | loggerStub = sinon.stub(logger, 'info').returns(Promise.resolve()); 46 | logger.info('one', 'two', 'three'); 47 | expect(loggerStub.calledOnce).to.be.true; 48 | expect(loggerStub.thisValues[0].level).to.be.equal('warn'); 49 | expect(loggerStub.calledWith('one', 'two', 'three')).to.be.true; 50 | }); 51 | 52 | it('tests logger (ERROR)', () => { 53 | const logger = new log.Logger(log.ERROR); 54 | loggerStub = sinon.stub(logger, 'info').returns(Promise.resolve()); 55 | logger.info('one', 'two', 'three'); 56 | expect(loggerStub.calledOnce).to.be.true; 57 | expect(loggerStub.thisValues[0].level).to.be.equal('error'); 58 | expect(loggerStub.calledWith('one', 'two', 'three')).to.be.true; 59 | }); 60 | }); 61 | 62 | describe('Wit', () => { 63 | let client = new Wit({ 64 | accessToken: process.env.WIT_TOKEN, 65 | }); 66 | 67 | it('tests that Wit has correct functions', () => { 68 | expect(typeof client.message).to.eql('function'); 69 | expect(typeof client.speech).to.eql('function'); 70 | }); 71 | 72 | it('tests message', () => { 73 | return client.message('Hello', {}).then(data => { 74 | expect(data.traits['wit$greetings'][0].value).to.be.equal('true'); 75 | expect(data.text).to.be.equal('Hello'); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('interactive', () => { 81 | it('checks that interactive exists', () => { 82 | expect(interactive).to.exists; 83 | }); 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /tests/wit-ai-basic-app-for-tests.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wit-ai/node-wit/8a5cfcbf5baf4d27ffca7e7e4d67556af708ef6c/tests/wit-ai-basic-app-for-tests.zip --------------------------------------------------------------------------------