├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── config.json ├── index.js ├── lib ├── actions │ ├── action.currency.js │ ├── action.forecast.msn.js │ ├── action.forecast.yahoo.js │ ├── action.math.js │ ├── action.movie.themoviedb.js │ ├── action.translator.js │ ├── action.wikipedia.js │ └── index.js ├── ava.js ├── helpers │ ├── compose.js │ ├── composeAsync.js │ ├── config.js │ ├── entities.js │ ├── factoryActions.js │ ├── factoryIntents.js │ ├── index.js │ ├── intersect.js │ ├── relation.js │ ├── request.js │ ├── resolve.js │ ├── store.js │ ├── syntax.js │ └── timeout.js ├── index.js ├── intent.js ├── intents │ ├── index.js │ ├── intent.any.js │ ├── intent.conversor.js │ ├── intent.movie.js │ ├── intent.translate.js │ └── intent.weather.js ├── listen.js └── processor │ ├── classifier.js │ ├── compromise.js │ ├── index.js │ ├── language.js │ ├── relations.js │ ├── sentiment.js │ ├── taxonomy.js │ ├── tokens.js │ └── translator.js ├── package.json ├── src ├── actions │ ├── action.currency.js │ ├── action.forecast.msn.js │ ├── action.forecast.yahoo.js │ ├── action.math.js │ ├── action.movie.themoviedb.js │ ├── action.translator.js │ ├── action.wikipedia.js │ └── index.js ├── ava.js ├── helpers │ ├── compose.js │ ├── composeAsync.js │ ├── config.js │ ├── entities.js │ ├── factoryActions.js │ ├── factoryIntents.js │ ├── index.js │ ├── intersect.js │ ├── relation.js │ ├── request.js │ ├── resolve.js │ ├── store.js │ ├── syntax.js │ └── timeout.js ├── index.js ├── intent.js ├── intents │ ├── index.js │ ├── intent.any.js │ ├── intent.conversor.js │ ├── intent.movie.js │ ├── intent.translate.js │ └── intent.weather.js ├── listen.js └── processor │ ├── classifier.js │ ├── compromise.js │ ├── index.js │ ├── language.js │ ├── relations.js │ ├── sentiment.js │ ├── taxonomy.js │ ├── tokens.js │ └── translator.js ├── test ├── actions │ ├── action.currency.spec.js │ ├── action.forecast.msn.spec.js │ ├── action.forecastYahoo.spec.js │ ├── action.math.spec.js │ ├── action.mock.js │ ├── action.movie.themoviedb.spec.js │ ├── action.translator.spec.js │ └── action.wikipedia.spec.js ├── ava.spec.js ├── helpers │ ├── compose.spec.js │ ├── composeAsync.spec.js │ ├── entities.spec.js │ ├── factoryActions.spec.js │ ├── factoryIntents.spec.js │ ├── intersect.spec.js │ ├── relation.spec.js │ ├── request.spec.js │ ├── resolve.spec.js │ ├── store.spec.js │ ├── syntax.spec.js │ └── timeout.spec.js ├── intent.spec.js ├── intents │ ├── intent.any.spec.js │ ├── intent.conversor.spec.js │ ├── intent.movie.spec.js │ ├── intent.translate.spec.js │ └── intent.weather.spec.js ├── listen.spec.js ├── processor │ ├── classifier.spec.js │ ├── compromise.spec.js │ ├── language.spec.js │ ├── relations.spec.js │ ├── sentiment.spec.js │ ├── taxonomy.spec.js │ ├── tokens.spec.js │ └── translator.spec.js └── terminal │ └── index.js ├── todos.md └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": ["transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | test/ 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["airbnb/base"], 4 | "rules": { 5 | "arrow-body-style": ["error", "as-needed"], 6 | "prefer-arrow-callback": 1, 7 | "space-before-function-paren": [2, "never"], 8 | "func-names": 0, 9 | "no-param-reassign": 0, 10 | "max-len": [1, 120, { "tabWidth": 2, "ignoreComments": true }], 11 | "prefer-template": 1, 12 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], 13 | "no-underscore-dangle": ["error", { "allowAfterThis": true }], 14 | "camelcase": [0, { properties: "never"}] 15 | }, 16 | "globals": { 17 | "atom": true 18 | }, 19 | "env": { 20 | "browser": true, 21 | "jest": true 22 | }, 23 | "plugins": [ 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directory 7 | node_modules 8 | .npm 9 | 10 | # Project 11 | store/* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.2.0" 4 | script: 5 | - npm test 6 | cache: 7 | directories: 8 | - node_modules 9 | env: 10 | - CXX=g++-4.8 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - g++-4.8 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) -t 714e179f-1084-4d46-b40e-91a99332d308 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | [![npm version](https://img.shields.io/npm/v/ava-ia.svg?style=flat-square)](https://www.npmjs.com/package/ava-ia) [![Build Status](http://img.shields.io/travis/ava-ia/core/master.svg?style=flat-square)](https://travis-ci.org/ava-ia/core) [![NPM Status](http://img.shields.io/npm/dm/ava-ia.svg?style=flat-square)](https://www.npmjs.org/package/ava-ia) [![devDependency Status](https://img.shields.io/david/ava-ia/core.svg?style=flat-square)](https://david-dm.org/ava-ia/core#info=dependencies) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square)](https://paypal.me/soyjavi) 4 | [![npm](https://img.shields.io/npm/l/botkit.svg?style=flat-square)](https://spdx.org/licenses/MIT) 5 | 6 | The main purpose of AVA (Agnostic Virtual Assistant) is create a clever/fast assistant for any kind of context. This repository concerns the *core* of AVA so feel free for try in your NodeJS projects. 7 | 8 | Nowadays we can find a lot of assistants, and more and more in the coming years, all of us know that Apps in the future will be more *conversational* and less *click/action*. For that reason our approach is create an agnostic and reusable system for help developers to create any kind of virtual assistants. 9 | 10 | This is an Open Source project, so any help will be welcomed. 11 | 12 | 13 | ## A little story about language processing... *and how Ava works*. 14 | 15 | If you have never worked with assistants/bots then you have to know that we need to analyze a given input and give it a semantic value. To do this often use NLP, *Natural Language Processing*. AVA in its case incorporates its own NLP but as you will see later we can use either. For example: 16 | 17 | > "I need an appointment with the dentist tomorrow at 2pm in London" 18 | 19 | AVA must understand your sentence and creates a *sentence relations scenario* like: 20 | + **SUBJECT** `I` 21 | + **ACTION** `need` 22 | + **VALUE** `1` 23 | + **OBJECT** `appointment` 24 | + **ITEM** `the dentist` 25 | + **WHEN** `Fri Jun 11 2016 14:00:00 GMT+0700 (ICT)` 26 | + **LOCATION** `London` 27 | 28 | Also gives you a *contextual information*: 29 | + **LANGUAGE** = `en` 30 | + **TYPE** = `declarative` 31 | + **SENTIMENT** = `0` (neutral) 32 | + **CLASSIFIER** = `/travel/transit` 33 | + **PROFILE** *(If previously user has talked with AVA, returns a history)* 34 | 35 | Ava depends on how you set up, but the next step is to process all the intents set. An intent is nothing more than a set of rules for scenarios *sentence relations* and *contextual information*. 36 | + **has LOCATION?** *yes, London* 37 | + **is negative SENTIMENT?** *no, is neutral* 38 | + **know WHEN?** *yes, tomorrow at 2pm* 39 | + 40 | 41 | If any intent is successful, it will be assigned an action (or more) which will be returned to the user in answer mode. 42 | + Set an appointment in phone's calendar like `${ITEM} in ${LOCATION} on ${DATE}` 43 | 44 | And that is, :) 45 | 46 | 47 | ## Installation 48 | 49 | AVA can be installed as an [npm package](https://www.npmjs.org/package/ava-ia): 50 | 51 | ```bash 52 | $ npm install --save ava-ia 53 | ``` 54 | 55 | 56 | ## Basic usage 57 | 58 | ```js 59 | import Ava from `ava-ia`; 60 | import { weather, movie } from `ava-ia/lib/intents`; 61 | import { forecastYahoo, forecastMSN, movieDB } from `ava-ia/lib/actions`; 62 | 63 | // 1. New instance 64 | const ava = new Ava({ 65 | debug: true // If you want see intents/actions trace log. 66 | }); 67 | 68 | // 2. Configure the intents 69 | ava 70 | .intent(weather, [forecastYahoo, forecastMSN]) 71 | .intent(movie, movieDB); 72 | 73 | // 3. Chat with Ava 74 | ava.listen('Do you know if tomorrow will rain in Bangkok?') 75 | .then(state => console.log(state)) 76 | .catch(error => console.log(state)) 77 | ``` 78 | 79 | 80 | ## Instance methods 81 | 82 | #### intent() 83 | The purpose of this method is to *teach* Ava about what kinds of things it can *answer* for you. As you read in the introduction the core of ava use *Intents* and *Actions* which are simple functions that receive a state and return it with an internal composition. 84 | 85 | The method `intent` is *chainable* that means you can attach all the intents you need, more intents means Ava is more clever 😉. This method takes two parameters: 86 | 87 | - `intent`: the *function* you wanna attach 88 | - `actions`: an action *function* (or *Array* of functions) those will call if the intent is is satisfactorily resolved. 89 | 90 | ```js 91 | import { weather } from `ava-ia/lib/intents`; 92 | import { forecastYahoo } from `ava-ia/lib/actions`; 93 | 94 | ava.intent(weather, forecastYahoo); 95 | ``` 96 | 97 | If we want attach two *actions* for the same *intent* just write: 98 | 99 | ```js 100 | import { forecastYahoo, forecastMSN } from `ava-ia/lib/actions`; 101 | 102 | ava.intent(weather, [forecastYahoo, forecastMSN]); 103 | ``` 104 | 105 | Ava will wait for the first successful action, that means it's like a race between the *actions* of a determinate *intent* and wins which finish first. If you wanna create a chain of `intents` it's quite easy: 106 | 107 | ```js 108 | import { weather, movie } from `ava-ia/lib/intents`; 109 | import { forecastYahoo, movieDB } from `ava-ia/lib/actions`; 110 | 111 | ava 112 | .intent(weather, forecastYahoo) 113 | .intent(movie, movieDB); 114 | ``` 115 | 116 | #### listen() 117 | The purpose of this method is *talk* with Ava. Just receive an `string` parameter and returns a `Promise`: 118 | 119 | ```js 120 | ava.listen('Do you know if tomorrow will rain in Bangkok?') 121 | .then(state => console.log(state)) 122 | .catch(error => console.log(state)) 123 | ``` 124 | 125 | If the promise is successful it will return a `object` with the state which contains the result of the processor and intents. The attributes of the *state* are: 126 | 127 | - `rawSentence`: contains an *string* with the origin sentence. 128 | - `language`: contains an *string* ISO code for language (cca2) of the sentence. 129 | - `sentence`: contains an *string* sentence translated to english 130 | - `taxonomy`: If `config.json` contains your [AlchemyAPI]() code containing an *array* of taxonomies. 131 | - `classifier`: contains an array of terms for identify the sense of the sentence. 132 | - `type`: *declarative*, *interrogative* or *exclamative* sentence. 133 | - `topics`: contains an *array* of most important terms of the sentence. 134 | - `tokens`: contains an *array* of rooted terms. 135 | - `relations`: contains an *object* with the sentence relations: 136 | + `subject` 137 | + `adverb` 138 | + `action` 139 | + `object` 140 | + `when` 141 | + `location` 142 | + `value` 143 | - `sentiment`: contains an *number* being `-5` most negative , `0` neutral and `+5` most positive. 144 | 145 | The most important attribute of *state* is `action` which contains an *object* with: 146 | - `engine`: a *string* with the name of the action 147 | - `ms`: contains the *number* of miliseconds waisted for resolve the action. 148 | - `entity`: a *string* describing the type of content of the action. 149 | - `title`: a *string* 150 | - `text`: a *string* (optional). 151 | - `value`: a *object* with explicit information about the content (optional). 152 | - `image`: a *stringURL* (optional). 153 | - `url`: a *url* with contains more info (optional). 154 | - `related`: a *object* with extra information (optional). 155 | - `date`: a *date* (optional). 156 | 157 | In the case that Ava can't find a action for our sentence it will return an error that we can capture in the `catch` method. 158 | 159 | 160 | ## Extend Ava with new *Intents* & *Actions* 161 | Extending Ava is quite easy, as you know all predefined *Intents* & *Actions* are stateless functions. So if you respect the input interface you can create your own Ava easily, lets see how. 162 | 163 | #### Create a new *Intent* 164 | Remember that when we set an intent in a determinate Ava instance we only need code: 165 | 166 | ```js 167 | import intentName from './intentName.js'; 168 | 169 | ava.intent(intentName, action); 170 | ``` 171 | 172 | Ava will process your intent definition and will queue it on intents list to execute. But... what is your intent definition?, well you will receive two parameters: 173 | - `state`: the actual *object* state. 174 | - `actions`: a *array* of actions to execute if intent is successful. 175 | 176 | Lets see the basic definition of your *intent*: 177 | 178 | **intentName.js** 179 | ```js 180 | import { resolve } from 'ava-ia/lib/helpers' 181 | 182 | export default (state, actions) => { 183 | resolve(state); 184 | }; 185 | ``` 186 | 187 | All intents must be *resolved* with the `state` (like a promise) but maybe your function it isn't a promise (async) for that reason we build the *helper* `resolve`. Just call it and your *intent* will be part of the factory of *intents*. Now we will see a complete example, our *intent* will: 188 | - check if a list of *terms* are part of `state` attributes `tokens` and `classifier` 189 | - check if the sentence has a specific syntax 190 | 191 | ```js 192 | 'use strict'; 193 | 194 | import { factoryActions, intersect, syntax, resolve } from 'ava-ia/lib/helpers' 195 | // -- Internal 196 | const TERMS = [ 'film', 'movie' ]; 197 | const RULES = [ 198 | '[Person] want see [Noun]', 199 | ]; 200 | 201 | export default (state, actions) => { 202 | const tokens = intersect(TERMS, state.tokens); 203 | const classifiers = intersect(TERMS, state.classifier); 204 | const match = syntax(state.sentence, RULES); 205 | 206 | if (tokens || classifiers || match) { 207 | return factoryActions(state, actions); 208 | } else { 209 | return resolve(state); 210 | } 211 | }; 212 | ``` 213 | 214 | As you can see, if we have `tokens`, or `classifiers` or `match` fulfilled we will call to our *actions* using the *helper* `factoryActions`. Easy right? 215 | 216 | #### Create a new *Action* 217 | Build your own *actions* is quite easy, like *intents* is just create a *stateless* function. Actions functions only receive one parameter: 218 | - `state`: the actual *object* state. 219 | 220 | successful functions have two ways for communicate the action: 221 | - `return` method for *sync* functions 222 | - `resolve` *Promise* method for *async* functions 223 | 224 | So the easiest and basic example could be: 225 | 226 | ```js 227 | export default (state) => { 228 | state.action = { value: 'Hello world!' }; 229 | return (state); 230 | } 231 | ``` 232 | 233 | As you can see we just create the attribute `action` and return the state to Ava. But life sometimes is more difficult, so now we will create an *async* Action which will request *something* to a external data source: 234 | 235 | ```js 236 | import { entities } from 'ava-ia/lib/helpers' 237 | 238 | export default (state) => { 239 | 240 | return new Promise( async (resolve, reject) => { 241 | response = await externalDataSource( {tokens: state.tokens} ); 242 | 243 | state.action = { 244 | engine: 'mock', 245 | type: entities.knowledge, 246 | value: response.value 247 | }; 248 | 249 | resolve(state); 250 | }); 251 | } 252 | ``` 253 | 254 | In this example we use `resolve` method 'cause we are inside a *Promise*, as you see still being easy create any kind of *actions*. 255 | 256 | 257 | ## Mastering in Ava 258 | If you wanna learn more about Ava *internals* please take a look to our [wiki](https://github.com/ava-ia/core/wiki). Feel free to offer new features, improvements or anything you can think of. This project makes sense with your participation and experience using Ava. 259 | 260 | ## Support 261 | 262 | ### Funding 263 | 264 | This software is provided to you as open source, free of charge. The time and effort to develop and maintain this project is dedicated by @soyjavi. If you (or your employer) benefit from this project, please consider a financial contribution. Your contribution helps continue the efforts that produce this and other open source software. 265 | 266 | Funds are accepted via [PayPal](https://paypal.me/soyjavi), any amount is appreciated. 267 | 268 | ## License 269 | 270 | Copyright (c) 2016 Javier Jimenez Villar 271 | 272 | Permission is hereby granted, free of charge, to any person obtaining a copy 273 | of this software and associated documentation files (the "Software"), to deal 274 | in the Software without restriction, including without limitation the rights 275 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 276 | copies of the Software, and to permit persons to whom the Software is 277 | furnished to do so, subject to the following conditions: 278 | 279 | The above copyright notice and this permission notice shall be included in 280 | all copies or substantial portions of the Software. 281 | 282 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 283 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 284 | FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE 285 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 286 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 287 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 288 | THE SOFTWARE. 289 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "timeout": 5000, 4 | 5 | "alchemy": { 6 | "url": "https://gateway-a.watsonplatform.net/calls", 7 | "apikey": "{your_api_key}" 8 | }, 9 | 10 | "themoviedb": { 11 | "url": "https://api.themoviedb.org/", 12 | "apikey": "{your_api_key}" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('./test/terminal') 3 | -------------------------------------------------------------------------------- /lib/actions/action.currency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _keys = require('babel-runtime/core-js/object/keys'); 8 | 9 | var _keys2 = _interopRequireDefault(_keys); 10 | 11 | var _promise = require('babel-runtime/core-js/promise'); 12 | 13 | var _promise2 = _interopRequireDefault(_promise); 14 | 15 | var _nodeFetch = require('node-fetch'); 16 | 17 | var _nodeFetch2 = _interopRequireDefault(_nodeFetch); 18 | 19 | var _helpers = require('../helpers'); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | // -- Internal 24 | var NAMES = { 25 | lev: 'BGN', 26 | real: 'BRL', 27 | franc: 'CHF', 28 | yuan: 'CNY', 29 | koruna: 'CZK', 30 | krone: 'DKK', 31 | pound: 'GBP', 32 | euro: 'EUR', 33 | kuna: 'HRK', 34 | forint: 'HUF', 35 | rupiah: 'IDR', 36 | shekel: 'ILS', 37 | rupee: 'INR', 38 | yen: 'JPY', 39 | won: 'KRW', 40 | peso: 'MXN', 41 | ringgit: 'MYR', 42 | crone: 'NOK', 43 | zloti: 'PLN', 44 | leu: 'RON', 45 | ruble: 'RUB', 46 | baht: 'THB', 47 | lira: 'TRY', 48 | dollar: 'USD', 49 | rand: 'ZAR' 50 | }; 51 | var getCurrency = function getCurrency(value) { 52 | return NAMES[value.toLowerCase()] || value.toUpperCase(); 53 | }; 54 | 55 | exports.default = function (state) { 56 | return new _promise2.default(function (resolve, reject) { 57 | var ms = new Date(); 58 | var match = (0, _helpers.syntax)(state.sentence, '[value] [currency] [preposition]? [currency]'); 59 | var from = getCurrency(match.currency[0]); 60 | var to = getCurrency(match.currency[1]); 61 | var value = parseFloat(match.value); 62 | 63 | if (state.debug) console.log('ActionCurrency'.bold.yellow, 'match:', match); 64 | 65 | (0, _nodeFetch2.default)('http://api.fixer.io/latest?base=' + from + '&symbols=' + to).then(function (response) { 66 | return response.json(); 67 | }).then(function (json) { 68 | if (json && json.rates && (0, _keys2.default)(json.rates).length > 0) { 69 | var conversion = value * json.rates[to]; 70 | state.action = { 71 | ms: new Date() - ms, 72 | engine: 'fixer.io', 73 | title: value + ' ' + from + ' are ' + conversion.toFixed(3) + ' ' + to, 74 | value: conversion, 75 | entity: _helpers.entities.object 76 | }; 77 | } 78 | resolve(state); 79 | }).catch(reject); 80 | }); 81 | }; -------------------------------------------------------------------------------- /lib/actions/action.forecast.msn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | var _moment = require('moment'); 12 | 13 | var _moment2 = _interopRequireDefault(_moment); 14 | 15 | var _weatherJs = require('weather-js'); 16 | 17 | var _weatherJs2 = _interopRequireDefault(_weatherJs); 18 | 19 | var _helpers = require('../helpers'); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | // -- Internal 24 | var RELATIONS = ['when', 'location']; 25 | 26 | var determineCondition = function determineCondition() { 27 | var condition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 28 | var forecast = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 29 | var when = arguments[2]; 30 | 31 | var value = { 32 | code: condition.skycode, 33 | condition: condition.skytext, 34 | temperature: condition.temperature, 35 | humidity: condition.humidity, 36 | wind: condition.windspeed, 37 | date: (0, _moment2.default)(condition.date, 'YYYY-MM-DD').format() 38 | }; 39 | 40 | if (when) { 41 | var day = forecast.find(function (item) { 42 | return (0, _moment2.default)(item.date, 'YYYY-MM-DD').isSame(when, 'day'); 43 | }); 44 | if (day) { 45 | value = { 46 | code: day.skycodeday, 47 | condition: day.skytextday, 48 | temperature: [day.low, day.high], 49 | date: (0, _moment2.default)(day.date, 'YYYY-MM-DD').format() 50 | }; 51 | } 52 | } 53 | return value; 54 | }; 55 | 56 | exports.default = function (state) { 57 | var _relation = (0, _helpers.relation)(RELATIONS, state), 58 | location = _relation.location, 59 | when = _relation.when; 60 | 61 | var ms = new Date(); 62 | 63 | return new _promise2.default(function (resolve) { 64 | if (state.debug) { 65 | console.log('ActionForecastMSN'.bold.yellow, 'location: ' + location + ', when: ' + when); 66 | } 67 | 68 | if (!location) return resolve((0, _helpers.request)(state, { relation: ['location'] })); 69 | 70 | return _weatherJs2.default.find({ search: location, degreeType: 'C' }, function (error, response) { 71 | if (!error) { 72 | var item = response[0]; 73 | var condition = determineCondition(item.current, item.forecast, when); 74 | state.action = { 75 | ms: new Date() - ms, 76 | engine: 'msn', 77 | entity: _helpers.entities.knowledge, 78 | title: 'Conditions for ' + item.location.name + ' at ' + item.current.observationtime, 79 | value: condition 80 | }; 81 | if (!when) state.action.related = item.forecast; 82 | 83 | resolve(state); 84 | } 85 | }); 86 | }); 87 | }; -------------------------------------------------------------------------------- /lib/actions/action.forecast.yahoo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | var _nodeFetch = require('node-fetch'); 12 | 13 | var _nodeFetch2 = _interopRequireDefault(_nodeFetch); 14 | 15 | var _moment = require('moment'); 16 | 17 | var _moment2 = _interopRequireDefault(_moment); 18 | 19 | var _helpers = require('../helpers'); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | // -- Internal 24 | var API = 'http://query.yahooapis.com/v1/public/yql?q='; 25 | var RELATIONS = ['when', 'location']; 26 | 27 | var determineCondition = function determineCondition() { 28 | var condition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 29 | var forecast = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 30 | var when = arguments[2]; 31 | 32 | var value = { 33 | code: condition.code, 34 | condition: condition.text, 35 | temperature: condition.temp, 36 | date: (0, _moment2.default)(condition.date, 'ddd, DD MMM YYYY hh:mm A ZZ').format() 37 | }; 38 | 39 | if (when) { 40 | var day = forecast.find(function (item) { 41 | return (0, _moment2.default)(item.date, 'DD MMM YYYY').isSame(when, 'day'); 42 | }); 43 | if (day) { 44 | value = { 45 | code: condition.code, 46 | condition: condition.text, 47 | temperature: [condition.low, condition.high], 48 | date: (0, _moment2.default)(day.date, 'DD MMM YYYY').format() 49 | }; 50 | } 51 | } 52 | 53 | return value; 54 | }; 55 | 56 | exports.default = function (state) { 57 | var _relation = (0, _helpers.relation)(RELATIONS, state), 58 | location = _relation.location, 59 | when = _relation.when; 60 | 61 | var ms = new Date(); 62 | var query = escape('select item from weather.forecast where woeid in (select woeid from geo.places where text=\'' + location + '\') and u=\'c\' | truncate(count=1)'); 63 | 64 | return new _promise2.default(function (resolve, reject) { 65 | if (state.debug) { 66 | console.log('ActionForecastYahoo'.bold.yellow, 'location: ' + location + ', when: ' + when); 67 | } 68 | if (!location) return resolve((0, _helpers.request)(state, { relation: ['location'] })); 69 | 70 | return (0, _nodeFetch2.default)('' + API + query + '&format=json').then(function (response) { 71 | return response.json(); 72 | }).then(function (body) { 73 | var item = body.query.results.channel.item; 74 | var condition = determineCondition(item.condition, item.forecast, when); 75 | state.action = { 76 | ms: new Date() - ms, 77 | engine: 'yahoo', 78 | entity: _helpers.entities.knowledge, 79 | title: item.title, 80 | url: item.link.split('*')[1], 81 | value: condition 82 | }; 83 | if (!when) state.action.related = item.forecast; 84 | 85 | resolve(state); 86 | }).catch(reject); 87 | }); 88 | }; -------------------------------------------------------------------------------- /lib/actions/action.math.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _getIterator2 = require('babel-runtime/core-js/get-iterator'); 8 | 9 | var _getIterator3 = _interopRequireDefault(_getIterator2); 10 | 11 | var _helpers = require('../helpers'); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | var SYNTAXES = ['. [Value] [Preposition] [Value]', '[Value] . [Preposition] [Value]', '[Value][Symbol][Value]', '[Value] . [Value]']; 16 | 17 | var OPERATIONS = [{ calc: function calc(a, b) { 18 | return a + b; 19 | }, terms: ['+', 'plus', 'add'] }, { calc: function calc(a, b) { 20 | return a - b; 21 | }, terms: ['-', 'minu', 'subtract'] }, { calc: function calc(a, b) { 22 | return a * b; 23 | }, terms: ['*', 'multiply'] }, { calc: function calc(a, b) { 24 | return a / b; 25 | }, terms: ['/', 'divided', 'divides'] }]; 26 | 27 | exports.default = function (state) { 28 | var ms = new Date(); 29 | var match = (0, _helpers.syntax)(state.sentence, SYNTAXES); 30 | if (!match) return (0, _helpers.resolve)(state); 31 | var operation = match.noun || match.conjunction || match.infinitive || match.symbol; 32 | var a = parseFloat(match.value[0]); 33 | var b = parseFloat(match.value[1]); 34 | 35 | if (state.debug) { 36 | console.log('ActionMath'.bold.yellow, 'operation:'.bold, operation, 'a:'.bold, a, 'b:'.bold, b); 37 | } 38 | 39 | if (operation && a && b) { 40 | var _iteratorNormalCompletion = true; 41 | var _didIteratorError = false; 42 | var _iteratorError = undefined; 43 | 44 | try { 45 | for (var _iterator = (0, _getIterator3.default)(OPERATIONS), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 46 | var type = _step.value; 47 | 48 | if (type.terms.indexOf(operation) > -1) { 49 | var value = type.calc(a, b); 50 | state.action = { 51 | ms: new Date() - ms, 52 | engine: 'ava', 53 | title: 'It\'s ' + value, 54 | value: value, 55 | entity: _helpers.entities.number 56 | }; 57 | 58 | break; 59 | } 60 | } 61 | } catch (err) { 62 | _didIteratorError = true; 63 | _iteratorError = err; 64 | } finally { 65 | try { 66 | if (!_iteratorNormalCompletion && _iterator.return) { 67 | _iterator.return(); 68 | } 69 | } finally { 70 | if (_didIteratorError) { 71 | throw _iteratorError; 72 | } 73 | } 74 | } 75 | } 76 | 77 | return (0, _helpers.resolve)(state); 78 | }; -------------------------------------------------------------------------------- /lib/actions/action.movie.themoviedb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | var _nodeFetch = require('node-fetch'); 12 | 13 | var _nodeFetch2 = _interopRequireDefault(_nodeFetch); 14 | 15 | var _helpers = require('../helpers'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | // -- Internal 20 | var credentials = (0, _helpers.config)('themoviedb'); 21 | var RELATIONS = ['object', 'subject']; 22 | 23 | var extract = function extract(data) { 24 | var item = { 25 | date: data.release_date || data.first_air_date, 26 | image: 'http://image.tmdb.org/t/p/w320' + (data.poster_path || data.profile_path), 27 | text: data.overview, 28 | title: data.title || data.name, 29 | value: { 30 | id: data.id, 31 | popularity: data.popularity, 32 | vote_average: data.vote_average 33 | } 34 | }; 35 | 36 | if (data.media_type === 'person') { 37 | item.entity = _helpers.entities.person; 38 | item.related = data.known_for.map(function (movie) { 39 | return extract(movie); 40 | }); 41 | } 42 | 43 | return item; 44 | }; 45 | 46 | exports.default = function (state) { 47 | if (!credentials) return state; 48 | 49 | return new _promise2.default(function (resolve) { 50 | var ms = new Date(); 51 | 52 | var _relation = (0, _helpers.relation)(RELATIONS, state), 53 | object = _relation.object, 54 | subject = _relation.subject; 55 | 56 | var query = object || subject || state.relations; 57 | if (state.debug) { 58 | console.log('ActionMovieDB'.bold.yellow, 'subject: ' + subject, 'object: ' + object); 59 | } 60 | 61 | (0, _nodeFetch2.default)(credentials.url + '/3/search/multi?api_key=' + credentials.apikey + '&query=' + query).then(function (response) { 62 | return response.json(); 63 | }).then(function (body) { 64 | var data = body.results[0]; 65 | if (data) { 66 | state.action = extract(data); 67 | state.action.ms = new Date() - ms; 68 | state.action.engine = 'themoviedb'; 69 | state.action.entity = _helpers.entities.knowledge; 70 | } 71 | 72 | resolve(state); 73 | }); 74 | }); 75 | }; -------------------------------------------------------------------------------- /lib/actions/action.translator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | exports.default = function (state) { 12 | return new _promise2.default(function (resolve, reject) { 13 | var actionIndex = state.tokens.indexOf('translate'); 14 | var terms = _nlp_compromise2.default.text(state.sentence).sentences[0].terms; 15 | var ms = new Date(); 16 | var to = void 0; 17 | var demonymIndex = void 0; 18 | var sentence = ''; 19 | 20 | terms.forEach(function (term, index) { 21 | if (index > actionIndex) { 22 | if (term.tag === DEMONYM && demonymIndex === undefined) { 23 | var demonym = term.text.toLowerCase(); 24 | var country = _worldCountries2.default.find(function (item) { 25 | return item.demonym.toLowerCase() === demonym; 26 | }); 27 | 28 | if (country) { 29 | demonymIndex = index; 30 | to = country.cca2; 31 | } 32 | } else if (index > demonymIndex) { 33 | sentence += term.text + ' '; 34 | } 35 | } 36 | }); 37 | 38 | if (state.debug) { 39 | console.log('ActionTranslator'.bold.yellow, 'sentence:'.bold, sentence, 'to:'.bold, to); 40 | } 41 | 42 | if (sentence && to) { 43 | (0, _googleTranslateApi2.default)(sentence, { to: to }).then(function (response) { 44 | state.action = { 45 | engine: 'google', 46 | ms: new Date() - ms, 47 | entity: _helpers.entities.knowledge, 48 | value: response.text 49 | }; 50 | resolve(state); 51 | }).catch(reject); 52 | } else { 53 | resolve(state); 54 | } 55 | }); 56 | }; 57 | 58 | var _worldCountries = require('world-countries'); 59 | 60 | var _worldCountries2 = _interopRequireDefault(_worldCountries); 61 | 62 | var _nlp_compromise = require('nlp_compromise'); 63 | 64 | var _nlp_compromise2 = _interopRequireDefault(_nlp_compromise); 65 | 66 | var _googleTranslateApi = require('google-translate-api'); 67 | 68 | var _googleTranslateApi2 = _interopRequireDefault(_googleTranslateApi); 69 | 70 | var _helpers = require('../helpers'); 71 | 72 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 73 | 74 | // -- Internal 75 | var DEMONYM = 'Demonym'; -------------------------------------------------------------------------------- /lib/actions/action.wikipedia.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | var _wtf_wikipedia = require('wtf_wikipedia'); 12 | 13 | var _wtf_wikipedia2 = _interopRequireDefault(_wtf_wikipedia); 14 | 15 | var _helpers = require('../helpers'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | // -- Internal 20 | var RELATIONS = ['object', 'subject', 'location']; 21 | var DOCUMENT_TERMS = [ 22 | // -- Common 23 | 'image', 'website', 24 | // 'links', 25 | 'iso_code', 26 | // -- Person 27 | 'birth_place', 'occupation', 28 | // -- Places 29 | 'pushpin_map', 'coordinates_region', 'population_total', 'population_netro', 'area_total_km2', 'utc_offset_DST']; 30 | /* -- @TODO ------------------------------------------------------------------- 31 | - Use document.infobox_template 32 | - Try to get image 33 | ----------------------------------------------------------------------------- */ 34 | var extract = function extract() { 35 | var properties = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 36 | 37 | var related = {}; 38 | 39 | DOCUMENT_TERMS.forEach(function (key) { 40 | if (properties[key] && properties[key].text) related[key] = properties[key].text; 41 | }); 42 | 43 | return related; 44 | }; 45 | 46 | exports.default = function (state) { 47 | var _relation = (0, _helpers.relation)(RELATIONS, state), 48 | object = _relation.object, 49 | subject = _relation.subject, 50 | location = _relation.location; 51 | 52 | var ms = new Date(); 53 | var concept = object || location || subject; 54 | 55 | return new _promise2.default(function (resolve) { 56 | if (state.debug) { 57 | console.log('ActionWikipedia'.bold.yellow, 'concept: ' + concept); 58 | } 59 | 60 | if (!concept) resolve(state); 61 | 62 | _wtf_wikipedia2.default.from_api(concept, 'en', function (response) { 63 | var document = _wtf_wikipedia2.default.parse(response); 64 | if (document.type === 'page' && document.categories.length > 0) { 65 | var summary = document.text.Intro.map(function (sentence) { 66 | return sentence.text; 67 | }).join(' '); 68 | 69 | state.action = { 70 | ms: new Date() - ms, 71 | engine: 'wikipedia', 72 | entity: _helpers.entities.knowledge, 73 | image: 'http://en.wikipedia.org/wiki/' + document.images[0], 74 | title: document.infobox.name ? document.infobox.name.text : concept, 75 | value: summary, 76 | related: extract(document.infobox) 77 | }; 78 | 79 | resolve(state); 80 | } 81 | }); 82 | }); 83 | }; -------------------------------------------------------------------------------- /lib/actions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _action = require('./action.currency'); 8 | 9 | Object.defineProperty(exports, 'currency', { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_action).default; 13 | } 14 | }); 15 | 16 | var _actionForecast = require('./action.forecast.yahoo'); 17 | 18 | Object.defineProperty(exports, 'forecastYahoo', { 19 | enumerable: true, 20 | get: function get() { 21 | return _interopRequireDefault(_actionForecast).default; 22 | } 23 | }); 24 | 25 | var _actionForecast2 = require('./action.forecast.msn'); 26 | 27 | Object.defineProperty(exports, 'forecastMSN', { 28 | enumerable: true, 29 | get: function get() { 30 | return _interopRequireDefault(_actionForecast2).default; 31 | } 32 | }); 33 | 34 | var _action2 = require('./action.math'); 35 | 36 | Object.defineProperty(exports, 'math', { 37 | enumerable: true, 38 | get: function get() { 39 | return _interopRequireDefault(_action2).default; 40 | } 41 | }); 42 | 43 | var _actionMovie = require('./action.movie.themoviedb'); 44 | 45 | Object.defineProperty(exports, 'movieDB', { 46 | enumerable: true, 47 | get: function get() { 48 | return _interopRequireDefault(_actionMovie).default; 49 | } 50 | }); 51 | 52 | var _action3 = require('./action.translator'); 53 | 54 | Object.defineProperty(exports, 'translator', { 55 | enumerable: true, 56 | get: function get() { 57 | return _interopRequireDefault(_action3).default; 58 | } 59 | }); 60 | 61 | var _action4 = require('./action.wikipedia'); 62 | 63 | Object.defineProperty(exports, 'wikipedia', { 64 | enumerable: true, 65 | get: function get() { 66 | return _interopRequireDefault(_action4).default; 67 | } 68 | }); 69 | 70 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/ava.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _assign = require('babel-runtime/core-js/object/assign'); 8 | 9 | var _assign2 = _interopRequireDefault(_assign); 10 | 11 | var _package = require('../package.json'); 12 | 13 | var _package2 = _interopRequireDefault(_package); 14 | 15 | var _intent = require('./intent'); 16 | 17 | var _intent2 = _interopRequireDefault(_intent); 18 | 19 | var _listen = require('./listen'); 20 | 21 | var _listen2 = _interopRequireDefault(_listen); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | // -- Functions 26 | exports.default = function () { 27 | var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 28 | 29 | var state = { 30 | version: _package2.default.version, 31 | intents: [], 32 | debug: props.debug || false 33 | }; 34 | 35 | return (0, _assign2.default)({}, (0, _intent2.default)(state), (0, _listen2.default)(state)); 36 | }; -------------------------------------------------------------------------------- /lib/helpers/compose.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function () { 8 | for (var _len = arguments.length, fns = Array(_len), _key = 0; _key < _len; _key++) { 9 | fns[_key] = arguments[_key]; 10 | } 11 | 12 | return function (val) { 13 | return fns.reduce(function (acc, fn) { 14 | return fn(acc); 15 | }, val); 16 | }; 17 | }; -------------------------------------------------------------------------------- /lib/helpers/composeAsync.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require("babel-runtime/core-js/promise"); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = function () { 14 | for (var _len = arguments.length, fns = Array(_len), _key = 0; _key < _len; _key++) { 15 | fns[_key] = arguments[_key]; 16 | } 17 | 18 | return function (state) { 19 | return fns.reduce(function (promise, fn) { 20 | return promise.then(fn); 21 | }, _promise2.default.resolve(state)); 22 | }; 23 | }; -------------------------------------------------------------------------------- /lib/helpers/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | // import path from 'path'; 7 | // const config = require(path.resolve('.', 'config.json')); 8 | 9 | var file = void 0; 10 | 11 | try { 12 | file = require(process.cwd() + '/config.json'); 13 | } catch (error) { 14 | file = {}; 15 | } 16 | 17 | exports.default = function (key) { 18 | return file[key]; 19 | }; -------------------------------------------------------------------------------- /lib/helpers/entities.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = { 7 | request: 0, 8 | person: 1, 9 | location: 2, 10 | object: 3, 11 | knowledge: 4, 12 | number: 5 13 | }; -------------------------------------------------------------------------------- /lib/helpers/factoryActions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _regenerator = require("babel-runtime/regenerator"); 8 | 9 | var _regenerator2 = _interopRequireDefault(_regenerator); 10 | 11 | var _promise = require("babel-runtime/core-js/promise"); 12 | 13 | var _promise2 = _interopRequireDefault(_promise); 14 | 15 | var _asyncToGenerator2 = require("babel-runtime/helpers/asyncToGenerator"); 16 | 17 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | exports.default = function () { 22 | var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(state, actions) { 23 | var promises; 24 | return _regenerator2.default.wrap(function _callee$(_context) { 25 | while (1) { 26 | switch (_context.prev = _context.next) { 27 | case 0: 28 | promises = actions.map(function (action) { 29 | return action.call(null, state); 30 | }); 31 | _context.next = 3; 32 | return _promise2.default.race(promises); 33 | 34 | case 3: 35 | return _context.abrupt("return", _context.sent); 36 | 37 | case 4: 38 | case "end": 39 | return _context.stop(); 40 | } 41 | } 42 | }, _callee, undefined); 43 | })); 44 | 45 | return function (_x, _x2) { 46 | return _ref.apply(this, arguments); 47 | }; 48 | }(); -------------------------------------------------------------------------------- /lib/helpers/factoryIntents.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _regenerator = require("babel-runtime/regenerator"); 8 | 9 | var _regenerator2 = _interopRequireDefault(_regenerator); 10 | 11 | var _promise = require("babel-runtime/core-js/promise"); 12 | 13 | var _promise2 = _interopRequireDefault(_promise); 14 | 15 | var _asyncToGenerator2 = require("babel-runtime/helpers/asyncToGenerator"); 16 | 17 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | exports.default = function () { 22 | var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(state) { 23 | var intents; 24 | return _regenerator2.default.wrap(function _callee$(_context) { 25 | while (1) { 26 | switch (_context.prev = _context.next) { 27 | case 0: 28 | intents = state.intents.map(function (intent) { 29 | return intent.script(state, intent.actions); 30 | }); 31 | _context.next = 3; 32 | return _promise2.default.race(intents); 33 | 34 | case 3: 35 | return _context.abrupt("return", _context.sent); 36 | 37 | case 4: 38 | case "end": 39 | return _context.stop(); 40 | } 41 | } 42 | }, _callee, undefined); 43 | })); 44 | 45 | return function (_x) { 46 | return _ref.apply(this, arguments); 47 | }; 48 | }(); -------------------------------------------------------------------------------- /lib/helpers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _compose = require('./compose'); 8 | 9 | Object.defineProperty(exports, 'compose', { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_compose).default; 13 | } 14 | }); 15 | 16 | var _composeAsync = require('./composeAsync'); 17 | 18 | Object.defineProperty(exports, 'composeAsync', { 19 | enumerable: true, 20 | get: function get() { 21 | return _interopRequireDefault(_composeAsync).default; 22 | } 23 | }); 24 | 25 | var _config = require('./config'); 26 | 27 | Object.defineProperty(exports, 'config', { 28 | enumerable: true, 29 | get: function get() { 30 | return _interopRequireDefault(_config).default; 31 | } 32 | }); 33 | 34 | var _entities = require('./entities'); 35 | 36 | Object.defineProperty(exports, 'entities', { 37 | enumerable: true, 38 | get: function get() { 39 | return _interopRequireDefault(_entities).default; 40 | } 41 | }); 42 | 43 | var _factoryActions = require('./factoryActions'); 44 | 45 | Object.defineProperty(exports, 'factoryActions', { 46 | enumerable: true, 47 | get: function get() { 48 | return _interopRequireDefault(_factoryActions).default; 49 | } 50 | }); 51 | 52 | var _factoryIntents = require('./factoryIntents'); 53 | 54 | Object.defineProperty(exports, 'factoryIntents', { 55 | enumerable: true, 56 | get: function get() { 57 | return _interopRequireDefault(_factoryIntents).default; 58 | } 59 | }); 60 | 61 | var _intersect = require('./intersect'); 62 | 63 | Object.defineProperty(exports, 'intersect', { 64 | enumerable: true, 65 | get: function get() { 66 | return _interopRequireDefault(_intersect).default; 67 | } 68 | }); 69 | 70 | var _relation = require('./relation'); 71 | 72 | Object.defineProperty(exports, 'relation', { 73 | enumerable: true, 74 | get: function get() { 75 | return _interopRequireDefault(_relation).default; 76 | } 77 | }); 78 | 79 | var _request = require('./request'); 80 | 81 | Object.defineProperty(exports, 'request', { 82 | enumerable: true, 83 | get: function get() { 84 | return _interopRequireDefault(_request).default; 85 | } 86 | }); 87 | 88 | var _resolve = require('./resolve'); 89 | 90 | Object.defineProperty(exports, 'resolve', { 91 | enumerable: true, 92 | get: function get() { 93 | return _interopRequireDefault(_resolve).default; 94 | } 95 | }); 96 | 97 | var _store = require('./store'); 98 | 99 | Object.defineProperty(exports, 'store', { 100 | enumerable: true, 101 | get: function get() { 102 | return _interopRequireDefault(_store).default; 103 | } 104 | }); 105 | 106 | var _syntax = require('./syntax'); 107 | 108 | Object.defineProperty(exports, 'syntax', { 109 | enumerable: true, 110 | get: function get() { 111 | return _interopRequireDefault(_syntax).default; 112 | } 113 | }); 114 | 115 | var _timeout = require('./timeout'); 116 | 117 | Object.defineProperty(exports, 'timeout', { 118 | enumerable: true, 119 | get: function get() { 120 | return _interopRequireDefault(_timeout).default; 121 | } 122 | }); 123 | 124 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/helpers/intersect.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function () { 8 | var array1 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 9 | var array2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 10 | return array1.filter(function (item) { 11 | return array2.indexOf(item) !== -1; 12 | }).length > 0; 13 | }; -------------------------------------------------------------------------------- /lib/helpers/relation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (keys, state) { 8 | var relations = state.relations || {}; 9 | var found = {}; 10 | keys.filter(function (key) { 11 | return relations[key] ? found[key] = relations[key].text : null; 12 | }); 13 | 14 | return found; 15 | }; -------------------------------------------------------------------------------- /lib/helpers/request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _entities = require('./entities'); 8 | 9 | var _entities2 = _interopRequireDefault(_entities); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = function (state, request) { 14 | state.action = { entity: _entities2.default.request, request: request }; 15 | 16 | return state; 17 | }; -------------------------------------------------------------------------------- /lib/helpers/resolve.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require("babel-runtime/core-js/promise"); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = function (state) { 14 | return new _promise2.default(function (resolve) { 15 | if (state.intents && state.intents.length === 1) resolve(state); 16 | }); 17 | }; -------------------------------------------------------------------------------- /lib/helpers/store.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _lowdb = require('lowdb'); 8 | 9 | var _lowdb2 = _interopRequireDefault(_lowdb); 10 | 11 | var _path = require('path'); 12 | 13 | var _path2 = _interopRequireDefault(_path); 14 | 15 | var _fs = require('fs'); 16 | 17 | var _fs2 = _interopRequireDefault(_fs); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | // -- Internal 22 | var folder = _path2.default.resolve('.', 'store'); // -- More info: https://github.com/typicode/lowdb 23 | 24 | 25 | if (!_fs2.default.existsSync(folder)) _fs2.default.mkdirSync(folder); 26 | 27 | exports.default = function (file) { 28 | var defaults = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 29 | 30 | var store = (0, _lowdb2.default)(folder + '/' + file, { storage: require('lowdb/lib/storages/file-async') }); 31 | 32 | if (defaults) store.defaults(defaults).value(); 33 | 34 | return store; 35 | }; -------------------------------------------------------------------------------- /lib/helpers/syntax.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _keys = require('babel-runtime/core-js/object/keys'); 8 | 9 | var _keys2 = _interopRequireDefault(_keys); 10 | 11 | var _getIterator2 = require('babel-runtime/core-js/get-iterator'); 12 | 13 | var _getIterator3 = _interopRequireDefault(_getIterator2); 14 | 15 | var _nlp_compromise = require('nlp_compromise'); 16 | 17 | var _nlp_compromise2 = _interopRequireDefault(_nlp_compromise); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | exports.default = function (sentence, rules) { 22 | if (!Array.isArray(rules)) rules = [rules]; 23 | var match = void 0; 24 | var rootSentence = _nlp_compromise2.default.text(sentence).root(); 25 | 26 | var _iteratorNormalCompletion = true; 27 | var _didIteratorError = false; 28 | var _iteratorError = undefined; 29 | 30 | try { 31 | for (var _iterator = (0, _getIterator3.default)(rules), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 32 | var rule = _step.value; 33 | 34 | var matches = _nlp_compromise2.default.text(rootSentence).match(rule); 35 | 36 | if (matches.length > 0 && matches[0] !== null) { 37 | var values = {}; 38 | 39 | var _iteratorNormalCompletion2 = true; 40 | var _didIteratorError2 = false; 41 | var _iteratorError2 = undefined; 42 | 43 | try { 44 | for (var _iterator2 = (0, _getIterator3.default)(matches[0].terms), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 45 | var term = _step2.value; 46 | 47 | var key = term.tag.toLowerCase(); 48 | var text = key !== 'symbol' ? _nlp_compromise2.default.text(term.text).root() : term.text; 49 | 50 | if (!values[key]) { 51 | values[key] = text; 52 | } else { 53 | if (!Array.isArray(values[key])) values[key] = [values[key]]; 54 | if (values[key].indexOf(text) === -1) { 55 | values[key].push(text); 56 | } 57 | } 58 | } 59 | } catch (err) { 60 | _didIteratorError2 = true; 61 | _iteratorError2 = err; 62 | } finally { 63 | try { 64 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 65 | _iterator2.return(); 66 | } 67 | } finally { 68 | if (_didIteratorError2) { 69 | throw _iteratorError2; 70 | } 71 | } 72 | } 73 | 74 | match = (0, _keys2.default)(values).length > 0 ? values : matches[0].text(); 75 | break; 76 | } 77 | } 78 | } catch (err) { 79 | _didIteratorError = true; 80 | _iteratorError = err; 81 | } finally { 82 | try { 83 | if (!_iteratorNormalCompletion && _iterator.return) { 84 | _iterator.return(); 85 | } 86 | } finally { 87 | if (_didIteratorError) { 88 | throw _iteratorError; 89 | } 90 | } 91 | } 92 | 93 | return match; 94 | }; -------------------------------------------------------------------------------- /lib/helpers/timeout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (reject) { 8 | var ms = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60000; 9 | return setTimeout(function () { 10 | reject(new Error("Timeout after " + ms + " ms")); 11 | }, ms); 12 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _ava = require('./ava'); 8 | 9 | var _ava2 = _interopRequireDefault(_ava); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = _ava2.default; -------------------------------------------------------------------------------- /lib/intent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var isFunction = function isFunction(value) { 7 | return typeof value === 'function'; 8 | }; 9 | 10 | exports.default = function (state) { 11 | return { 12 | intent: function intent(script, actions) { 13 | if (isFunction(actions)) actions = [actions]; 14 | 15 | if (isFunction(script) && Array.isArray(actions)) { 16 | state.intents.push({ script: script, actions: actions }); 17 | } 18 | 19 | return this; 20 | } 21 | }; 22 | }; -------------------------------------------------------------------------------- /lib/intents/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _intent = require('./intent.any'); 8 | 9 | Object.defineProperty(exports, 'any', { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_intent).default; 13 | } 14 | }); 15 | 16 | var _intent2 = require('./intent.conversor'); 17 | 18 | Object.defineProperty(exports, 'conversor', { 19 | enumerable: true, 20 | get: function get() { 21 | return _interopRequireDefault(_intent2).default; 22 | } 23 | }); 24 | 25 | var _intent3 = require('./intent.movie'); 26 | 27 | Object.defineProperty(exports, 'movie', { 28 | enumerable: true, 29 | get: function get() { 30 | return _interopRequireDefault(_intent3).default; 31 | } 32 | }); 33 | 34 | var _intent4 = require('./intent.translate'); 35 | 36 | Object.defineProperty(exports, 'translate', { 37 | enumerable: true, 38 | get: function get() { 39 | return _interopRequireDefault(_intent4).default; 40 | } 41 | }); 42 | 43 | var _intent5 = require('./intent.weather'); 44 | 45 | Object.defineProperty(exports, 'weather', { 46 | enumerable: true, 47 | get: function get() { 48 | return _interopRequireDefault(_intent5).default; 49 | } 50 | }); 51 | 52 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/intents/intent.any.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _helpers = require('../helpers'); 8 | 9 | exports.default = function (state, actions) { 10 | if (state.debug) console.log('IntentAny'.bold.green); 11 | 12 | return (0, _helpers.factoryActions)(state, actions); 13 | }; -------------------------------------------------------------------------------- /lib/intents/intent.conversor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _helpers = require('../helpers'); 8 | 9 | // -- Internal 10 | var RULES = ['how much is [value] [currency] [preposition] [currency]', 'convert [value] [currency] [preposition] [currency]']; 11 | 12 | exports.default = function (state, actions) { 13 | var match = (0, _helpers.syntax)(state.sentence, RULES); 14 | if (state.debug) console.log('IntentConversor'.bold.green, 'match:'.bold, match); 15 | 16 | return match ? (0, _helpers.factoryActions)(state, actions) : (0, _helpers.resolve)(state); 17 | }; -------------------------------------------------------------------------------- /lib/intents/intent.movie.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _helpers = require('../helpers'); 8 | 9 | // -- Internal 10 | var TERMS = ['film', 'movie', 'show', 'actor', 'director', 'camera', 'editor', 'cinema', 'tv', 'producer']; 11 | 12 | exports.default = function (state, actions) { 13 | var tokens = (0, _helpers.intersect)(TERMS, state.tokens); 14 | var classifiers = (0, _helpers.intersect)(TERMS, state.classifier); 15 | if (state.debug) { 16 | console.log('IntentMovie'.bold.green, 'tokens: ' + tokens.toString().green + ', classifiers: ' + classifiers.toString().green); 17 | } 18 | 19 | return tokens || classifiers ? (0, _helpers.factoryActions)(state, actions) : (0, _helpers.resolve)(state); 20 | }; -------------------------------------------------------------------------------- /lib/intents/intent.translate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _helpers = require('../helpers'); 8 | 9 | // -- Internal 10 | var RULES = ['translate [Preposition]? [Demonym]', 'translate * [Preposition] [Demonym]']; 11 | 12 | exports.default = function (state, actions) { 13 | var match = (0, _helpers.syntax)(state.sentence, RULES); 14 | if (state.debug) console.log('IntentTranslate'.bold.green, 'match:'.bold, match); 15 | 16 | return match ? (0, _helpers.factoryActions)(state, actions) : (0, _helpers.resolve)(state); 17 | }; -------------------------------------------------------------------------------- /lib/intents/intent.weather.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _helpers = require('../helpers'); 8 | 9 | // -- Internal 10 | var TERMS = ['weather', 'umbrella', 'rain', 'forecast', 'snow', 'fog', 'sun', 'cloud', 'meteo']; 11 | 12 | exports.default = function (state, actions) { 13 | var tokens = (0, _helpers.intersect)(TERMS, state.tokens); 14 | var classifiers = (0, _helpers.intersect)(TERMS, state.classifier); 15 | if (state.debug) { 16 | console.log('IntentWeather'.bold.green, 'tokens: ' + tokens.toString().green + ', classifiers: ' + classifiers.toString().green); 17 | } 18 | 19 | return tokens || classifiers ? (0, _helpers.factoryActions)(state, actions) : (0, _helpers.resolve)(state); 20 | }; -------------------------------------------------------------------------------- /lib/listen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | var _helpers = require('./helpers'); 12 | 13 | var _processor = require('./processor'); 14 | 15 | var _processor2 = _interopRequireDefault(_processor); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | exports.default = function (state) { 20 | return { 21 | listen: function listen(sentence, ms) { 22 | return new _promise2.default(function (resolve, reject) { 23 | state.rawSentence = sentence; 24 | 25 | if (ms) (0, _helpers.timeout)(reject, ms); 26 | var factory = (0, _helpers.composeAsync)(_processor2.default, _helpers.factoryIntents); 27 | 28 | factory(state).then(function () { 29 | if (state.action) { 30 | resolve(state); 31 | } else { 32 | reject(new Error('Unknown action')); 33 | } 34 | }).catch(function (error) { 35 | if (!error) error = { code: 0, message: "Sorry, I haven't understood you" }; 36 | reject(error); 37 | }); 38 | }); 39 | } 40 | }; 41 | }; -------------------------------------------------------------------------------- /lib/processor/classifier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _bayes = require('bayes'); 8 | 9 | var _bayes2 = _interopRequireDefault(_bayes); 10 | 11 | var _helpers = require('../helpers'); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | // -- Internal 16 | // -- More info: https://github.com/ttezel/bayes 17 | var db = (0, _helpers.store)('classifier.json'); 18 | var classifierForLanguage = function classifierForLanguage(language) { 19 | var value = db.get(language).value(); 20 | return value ? _bayes2.default.fromJson(value) : (0, _bayes2.default)(); 21 | }; 22 | 23 | exports.default = function (state) { 24 | var classifier = classifierForLanguage(state.language); 25 | var categories = classifier.categorize(state.rawSentence); 26 | 27 | if (state.taxonomy && state.taxonomy !== categories) { 28 | classifier.learn(state.rawSentence, state.taxonomy); 29 | db.set(state.language, classifier.toJson()).value(); 30 | categories = state.taxonomy; 31 | } 32 | state.classifier = categories ? categories.split('/') : []; 33 | 34 | return state; 35 | }; -------------------------------------------------------------------------------- /lib/processor/compromise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _nlp_compromise = require('nlp_compromise'); 8 | 9 | var _nlp_compromise2 = _interopRequireDefault(_nlp_compromise); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = function (state) { 14 | var compromise = _nlp_compromise2.default.text(state.sentence); 15 | 16 | state.type = _nlp_compromise2.default.sentence(state.sentence).sentence_type(); 17 | state.topics = compromise.topics().map(function (topic) { 18 | return topic.text; 19 | }); 20 | state.tags = compromise.tags()[0]; 21 | 22 | return state; 23 | }; -------------------------------------------------------------------------------- /lib/processor/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _helpers = require('../helpers'); 8 | 9 | var _language = require('./language'); 10 | 11 | var _language2 = _interopRequireDefault(_language); 12 | 13 | var _translator = require('./translator'); 14 | 15 | var _translator2 = _interopRequireDefault(_translator); 16 | 17 | var _classifier = require('./classifier'); 18 | 19 | var _classifier2 = _interopRequireDefault(_classifier); 20 | 21 | var _compromise = require('./compromise'); 22 | 23 | var _compromise2 = _interopRequireDefault(_compromise); 24 | 25 | var _taxonomy = require('./taxonomy'); 26 | 27 | var _taxonomy2 = _interopRequireDefault(_taxonomy); 28 | 29 | var _tokens = require('./tokens'); 30 | 31 | var _tokens2 = _interopRequireDefault(_tokens); 32 | 33 | var _relations = require('./relations'); 34 | 35 | var _relations2 = _interopRequireDefault(_relations); 36 | 37 | var _sentiment = require('./sentiment'); 38 | 39 | var _sentiment2 = _interopRequireDefault(_sentiment); 40 | 41 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 42 | 43 | exports.default = function (state) { 44 | var factory = (0, _helpers.composeAsync)(_language2.default, _translator2.default, _taxonomy2.default, _classifier2.default, _compromise2.default, _tokens2.default, _relations2.default, _sentiment2.default); 45 | 46 | return factory(state); 47 | }; -------------------------------------------------------------------------------- /lib/processor/language.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | exports.default = function (state) { 12 | return new _promise2.default(function (resolve) { 13 | _cld2.default.detect(state.rawSentence, function (error, value) { 14 | if (!error) { 15 | state.language = value.languages[0].code; 16 | } 17 | state.sentence = state.rawSentence; 18 | 19 | resolve(state); 20 | }); 21 | }); 22 | }; 23 | 24 | var _cld = require('cld'); 25 | 26 | var _cld2 = _interopRequireDefault(_cld); 27 | 28 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/processor/relations.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _nlp_compromise = require('nlp_compromise'); 8 | 9 | var _nlp_compromise2 = _interopRequireDefault(_nlp_compromise); 10 | 11 | var _chronoNode = require('chrono-node'); 12 | 13 | var _chronoNode2 = _interopRequireDefault(_chronoNode); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | // -- Internal 18 | var TERMS_RELATIONS = { 19 | person: 'subject', 20 | adverb: 'adverb', 21 | verb: 'action', 22 | noun: 'object', 23 | date: 'when', 24 | place: 'location', 25 | city: 'location', 26 | value: 'value' 27 | }; 28 | var lexicon = _nlp_compromise2.default.lexicon(); 29 | lexicon.ava = 'Person'; 30 | lexicon.here = 'Place'; 31 | 32 | // -- Private methods 33 | var extractRelation = function extractRelation(tag, term, previous) { 34 | var relation = {}; 35 | var text = term.normal || term.text; 36 | 37 | switch (tag) { 38 | case 'date': 39 | { 40 | text = _chronoNode2.default.parseDate(text); 41 | break; 42 | } 43 | 44 | case 'verb': 45 | { 46 | var compromiseVerb = _nlp_compromise2.default.verb(term.expansion || term.text); 47 | if (!previous) { 48 | relation.verb = { 49 | tense: compromiseVerb.conjugation().toLowerCase().split('tense')[0], 50 | negative: compromiseVerb.isNegative() 51 | }; 52 | } else { 53 | relation.verb = previous.verb; 54 | } 55 | text = compromiseVerb.conjugate().infinitive; 56 | break; 57 | } 58 | 59 | case 'noun': 60 | { 61 | text = _nlp_compromise2.default.text(text).root(); 62 | break; 63 | } 64 | 65 | case 'person': 66 | { 67 | tag = term.pos.Pronoun ? 'pronoun' : tag; 68 | break; 69 | } 70 | 71 | default: 72 | break; 73 | } 74 | 75 | relation.tag = tag; 76 | relation.text = text; 77 | 78 | return relation; 79 | }; 80 | 81 | exports.default = function (state) { 82 | var sentence = state.sentence || _nlp_compromise2.default.text(state.sentence).normal(); 83 | var compromiseSentences = _nlp_compromise2.default.text(sentence, { lexicon: lexicon }).sentences; 84 | var terms = compromiseSentences[0] ? compromiseSentences[0].terms : []; 85 | var relations = {}; 86 | 87 | terms.forEach(function (term) { 88 | var tag = (term.pos.Verb ? 'verb' : term.tag).toLowerCase(); 89 | var relation = TERMS_RELATIONS[tag]; 90 | 91 | if (relation) { 92 | if (relation === TERMS_RELATIONS.person && relations[relation]) { 93 | relation = relations[relation] !== (term.normal || term.text) ? TERMS_RELATIONS.noun : undefined; 94 | } 95 | if (relation) relations[relation] = extractRelation(tag, term, relations[relation]); 96 | } 97 | }); 98 | state.relations = relations; 99 | 100 | return state; 101 | }; -------------------------------------------------------------------------------- /lib/processor/sentiment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _sentiment = require('sentiment'); 8 | 9 | var _sentiment2 = _interopRequireDefault(_sentiment); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = function (state) { 14 | var sentence = (0, _sentiment2.default)(state.sentence.toLowerCase()); 15 | state.sentiment = sentence.score; 16 | 17 | return state; 18 | }; -------------------------------------------------------------------------------- /lib/processor/taxonomy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | var _alchemyApi = require('alchemy-api'); 12 | 13 | var _alchemyApi2 = _interopRequireDefault(_alchemyApi); 14 | 15 | var _helpers = require('../helpers'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | // -- Internal 20 | var credentials = (0, _helpers.config)('alchemy'); 21 | var processor = void 0; 22 | if (credentials) processor = new _alchemyApi2.default(credentials.apikey); 23 | 24 | exports.default = function (state) { 25 | if (!credentials) return state; 26 | 27 | return new _promise2.default(function (resolve) { 28 | processor.taxonomies(state.sentence, {}, function (error, response) { 29 | if (error || !response.taxonomy) return resolve(state); 30 | 31 | var taxonomy = response.taxonomy[0]; 32 | if (taxonomy) { 33 | state.taxonomy = taxonomy.label.charAt(0) === '/' ? taxonomy.label.slice(1) : taxonomy.label; 34 | } 35 | 36 | return resolve(state); 37 | }); 38 | }); 39 | }; -------------------------------------------------------------------------------- /lib/processor/tokens.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _nlp_compromise = require('nlp_compromise'); 8 | 9 | var _nlp_compromise2 = _interopRequireDefault(_nlp_compromise); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = function (state) { 14 | state.tokens = _nlp_compromise2.default.text(state.sentence).root().split(' '); 15 | 16 | return state; 17 | }; -------------------------------------------------------------------------------- /lib/processor/translator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _promise = require('babel-runtime/core-js/promise'); 8 | 9 | var _promise2 = _interopRequireDefault(_promise); 10 | 11 | exports.default = function (state) { 12 | return new _promise2.default(function (resolve, reject) { 13 | if (state.language === LANGUAGE) return resolve(state); 14 | 15 | return (0, _googleTranslateApi2.default)(state.rawSentence, { from: state.language, to: LANGUAGE }).then(function (response) { 16 | state.language = response.from.language.iso; 17 | state.sentence = response.text; 18 | resolve(state); 19 | }).catch(function (error) { 20 | reject(error); 21 | }); 22 | }); 23 | }; 24 | 25 | var _googleTranslateApi = require('google-translate-api'); 26 | 27 | var _googleTranslateApi2 = _interopRequireDefault(_googleTranslateApi); 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | // -- Internal 32 | var LANGUAGE = 'en'; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ava-ia", 3 | "description": "Agnostic virtual assistant", 4 | "version": "0.1.2", 5 | "main": "./lib/index.js", 6 | "homepage": "http://github.com/ava-ia", 7 | "author": { 8 | "name": "AVAia Team", 9 | "url": "http://github.com/ava-ia" 10 | }, 11 | "contributors": [ 12 | { 13 | "name": "Javi Jimenez Villar", 14 | "email": "hello@soyjavi.com", 15 | "url": "http://soyjavi.com/" 16 | } 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/ava-ia/core.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/ava-ia/core/issues", 24 | "email": "issues@react-toolbox.com" 25 | }, 26 | "scripts": { 27 | "start": "npm run build & npm run terminal", 28 | "test": "npm run mocha", 29 | "build": "babel src --out-dir lib", 30 | "terminal": "node index.js Ava, Do you know if tomorrow will rain in Chiang Mai?", 31 | "lint": "eslint src/**", 32 | "mocha": "mocha --recursive --compilers js:babel-register --require babel-polyfill", 33 | "singletest": "mocha test/actions/*currency*.js --compilers js:babel-register --require babel-polyfill" 34 | }, 35 | "license": "MIT", 36 | "dependencies": { 37 | "alchemy-api": "1.3.3", 38 | "bayes": "0.0.7", 39 | "chrono-node": "1.3.5", 40 | "cld": "2.4.5", 41 | "google-translate-api": "2.3.0", 42 | "lowdb": "0.16.2", 43 | "moment": "2.18.1", 44 | "nlp_compromise": "6.5.3", 45 | "node-fetch": "1.7.1", 46 | "sentiment": "4.0.0", 47 | "weather-js": "2.0.0", 48 | "world-countries": "1.8.1", 49 | "wtf_wikipedia": "1.0.1" 50 | }, 51 | "devDependencies": { 52 | "babel-cli": "^6.24.1", 53 | "babel-core": "^6.25.0", 54 | "babel-eslint": "^6.1.2", 55 | "babel-plugin-transform-runtime": "^6.23.0", 56 | "babel-preset-es2015": "^6.24.1", 57 | "babel-preset-stage-0": "^6.24.1", 58 | "babel-register": "^6.24.1", 59 | "chai": "^4.1.0", 60 | "chai-as-promised": "^7.1.1", 61 | "colors": "^1.1.2", 62 | "eslint": "^3.2.2", 63 | "eslint-config-airbnb": "^10.0.0", 64 | "eslint-plugin-import": "^1.12.0", 65 | "mocha": "^3.4.2" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/actions/action.currency.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { entities, syntax } from '../helpers'; 3 | // -- Internal 4 | const NAMES = { 5 | lev: 'BGN', 6 | real: 'BRL', 7 | franc: 'CHF', 8 | yuan: 'CNY', 9 | koruna: 'CZK', 10 | krone: 'DKK', 11 | pound: 'GBP', 12 | euro: 'EUR', 13 | kuna: 'HRK', 14 | forint: 'HUF', 15 | rupiah: 'IDR', 16 | shekel: 'ILS', 17 | rupee: 'INR', 18 | yen: 'JPY', 19 | won: 'KRW', 20 | peso: 'MXN', 21 | ringgit: 'MYR', 22 | crone: 'NOK', 23 | zloti: 'PLN', 24 | leu: 'RON', 25 | ruble: 'RUB', 26 | baht: 'THB', 27 | lira: 'TRY', 28 | dollar: 'USD', 29 | rand: 'ZAR', 30 | }; 31 | const getCurrency = (value) => (NAMES[value.toLowerCase()] || value.toUpperCase()); 32 | 33 | export default (state) => { 34 | const ms = new Date(); 35 | 36 | return new Promise((resolve, reject) => { 37 | const match = syntax(state.sentence, '[value] [currency] [preposition]? [currency]'); 38 | if (!match) return reject(); 39 | 40 | const from = getCurrency(match.currency[0]); 41 | const to = getCurrency(match.currency[1]); 42 | const value = parseFloat(match.value); 43 | 44 | if (state.debug) console.log('ActionCurrency'.bold.yellow, 'match:', match); 45 | 46 | return fetch(`http://api.fixer.io/latest?base=${from}&symbols=${to}`) 47 | .then((response) => response.json()) 48 | .then((json) => { 49 | if (json && json.rates && Object.keys(json.rates).length > 0) { 50 | const conversion = value * json.rates[to]; 51 | state.action = { 52 | ms: (new Date() - ms), 53 | engine: 'fixer.io', 54 | title: `${value} ${from} are ${conversion.toFixed(3)} ${to}`, 55 | value: conversion, 56 | entity: entities.object, 57 | }; 58 | } 59 | resolve(state); 60 | }) 61 | .catch(reject); 62 | }); 63 | }; 64 | -------------------------------------------------------------------------------- /src/actions/action.forecast.msn.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import weather from 'weather-js'; 3 | import { entities, relation, request } from '../helpers'; 4 | // -- Internal 5 | const RELATIONS = ['when', 'location']; 6 | 7 | const determineCondition = function(condition = {}, forecast = [], when) { 8 | let value = { 9 | code: condition.skycode, 10 | condition: condition.skytext, 11 | temperature: condition.temperature, 12 | humidity: condition.humidity, 13 | wind: condition.windspeed, 14 | date: moment(condition.date, 'YYYY-MM-DD').format(), 15 | }; 16 | 17 | if (when) { 18 | const day = forecast.find((item) => moment(item.date, 'YYYY-MM-DD').isSame(when, 'day')); 19 | if (day) { 20 | value = { 21 | code: day.skycodeday, 22 | condition: day.skytextday, 23 | temperature: [day.low, day.high], 24 | date: moment(day.date, 'YYYY-MM-DD').format(), 25 | }; 26 | } 27 | } 28 | return value; 29 | }; 30 | 31 | export default (state) => { 32 | const { location, when } = relation(RELATIONS, state); 33 | const ms = new Date(); 34 | 35 | return new Promise((resolve) => { 36 | if (state.debug) { 37 | console.log('ActionForecastMSN'.bold.yellow, `location: ${location}, when: ${when}`); 38 | } 39 | 40 | if (!location) return resolve(request(state, { relation: ['location'] })); 41 | 42 | return weather.find({ search: location, degreeType: 'C' }, (error, response) => { 43 | if (!error) { 44 | const item = response[0]; 45 | const condition = determineCondition(item.current, item.forecast, when); 46 | state.action = { 47 | ms: (new Date() - ms), 48 | engine: 'msn', 49 | entity: entities.knowledge, 50 | title: `Conditions for ${item.location.name} at ${item.current.observationtime}`, 51 | value: condition, 52 | }; 53 | if (!when) state.action.related = item.forecast; 54 | 55 | resolve(state); 56 | } 57 | }); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /src/actions/action.forecast.yahoo.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import moment from 'moment'; 3 | import { entities, relation, request } from '../helpers'; 4 | 5 | // -- Internal 6 | const API = 'http://query.yahooapis.com/v1/public/yql?q='; 7 | const RELATIONS = ['when', 'location']; 8 | 9 | const determineCondition = (condition = {}, forecast = [], when) => { 10 | let value = { 11 | code: condition.code, 12 | condition: condition.text, 13 | temperature: condition.temp, 14 | date: moment(condition.date, 'ddd, DD MMM YYYY hh:mm A ZZ').format(), 15 | }; 16 | 17 | if (when) { 18 | const day = forecast.find((item) => moment(item.date, 'DD MMM YYYY').isSame(when, 'day')); 19 | if (day) { 20 | value = { 21 | code: condition.code, 22 | condition: condition.text, 23 | temperature: [condition.low, condition.high], 24 | date: moment(day.date, 'DD MMM YYYY').format(), 25 | }; 26 | } 27 | } 28 | 29 | return value; 30 | }; 31 | 32 | export default (state) => { 33 | const { location, when } = relation(RELATIONS, state); 34 | const ms = new Date(); 35 | const query = escape(`select item from weather.forecast where woeid in (select woeid from geo.places where text='${location}') and u='c' | truncate(count=1)`); 36 | 37 | return new Promise((resolve, reject) => { 38 | if (state.debug) { 39 | console.log('ActionForecastYahoo'.bold.yellow, `location: ${location}, when: ${when}`); 40 | } 41 | if (!location) return resolve(request(state, { relation: ['location'] })); 42 | 43 | return fetch(`${API}${query}&format=json`) 44 | .then(response => response.json()) 45 | .then(body => { 46 | const item = body.query.results.channel.item; 47 | const condition = determineCondition(item.condition, item.forecast, when); 48 | state.action = { 49 | ms: (new Date() - ms), 50 | engine: 'yahoo', 51 | entity: entities.knowledge, 52 | title: item.title, 53 | url: item.link.split('*')[1], 54 | value: condition, 55 | }; 56 | if (!when) state.action.related = item.forecast; 57 | 58 | resolve(state); 59 | }) 60 | .catch(reject); 61 | }); 62 | }; 63 | -------------------------------------------------------------------------------- /src/actions/action.math.js: -------------------------------------------------------------------------------- 1 | import { entities, resolve, syntax } from '../helpers'; 2 | 3 | const SYNTAXES = [ 4 | '. [Value] [Preposition] [Value]', 5 | '[Value] . [Preposition] [Value]', 6 | '[Value][Symbol][Value]', 7 | '[Value] . [Value]', 8 | ]; 9 | 10 | const OPERATIONS = [ 11 | { calc: (a, b) => a + b, terms: ['+', 'plus', 'add'] }, 12 | { calc: (a, b) => a - b, terms: ['-', 'minu', 'subtract'] }, 13 | { calc: (a, b) => a * b, terms: ['*', 'multiply'] }, 14 | { calc: (a, b) => a / b, terms: ['/', 'divided', 'divides'] }, 15 | ]; 16 | 17 | export default (state) => { 18 | const ms = new Date(); 19 | const match = syntax(state.sentence, SYNTAXES); 20 | if (!match) return resolve(state); 21 | const operation = match.noun || match.conjunction || match.infinitive || match.symbol; 22 | const a = parseFloat(match.value[0]); 23 | const b = parseFloat(match.value[1]); 24 | 25 | if (state.debug) { 26 | console.log('ActionMath'.bold.yellow, 'operation:'.bold, operation, 'a:'.bold, a, 'b:'.bold, b); 27 | } 28 | 29 | if (operation && a && b) { 30 | for (const type of OPERATIONS) { 31 | if (type.terms.indexOf(operation) > -1) { 32 | const value = type.calc(a, b); 33 | state.action = { 34 | ms: (new Date() - ms), 35 | engine: 'ava', 36 | title: `It's ${value}`, 37 | value, 38 | entity: entities.number, 39 | }; 40 | 41 | break; 42 | } 43 | } 44 | } 45 | 46 | return resolve(state); 47 | }; 48 | -------------------------------------------------------------------------------- /src/actions/action.movie.themoviedb.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { config, entities, relation } from '../helpers'; 3 | // -- Internal 4 | const credentials = config('themoviedb'); 5 | const RELATIONS = ['object', 'subject']; 6 | 7 | const extract = (data) => { 8 | const item = { 9 | date: data.release_date || data.first_air_date, 10 | image: `http://image.tmdb.org/t/p/w320${data.poster_path || data.profile_path}`, 11 | text: data.overview, 12 | title: data.title || data.name, 13 | value: { 14 | id: data.id, 15 | popularity: data.popularity, 16 | vote_average: data.vote_average, 17 | }, 18 | }; 19 | 20 | if (data.media_type === 'person') { 21 | item.entity = entities.person; 22 | item.related = data.known_for.map(movie => extract(movie)); 23 | } 24 | 25 | return item; 26 | }; 27 | 28 | export default (state) => { 29 | if (!credentials) return (state); 30 | 31 | return new Promise((resolve) => { 32 | const ms = new Date(); 33 | const { object, subject } = relation(RELATIONS, state); 34 | const query = object || subject || state.relations; 35 | if (state.debug) { 36 | console.log('ActionMovieDB'.bold.yellow, `subject: ${subject}`, `object: ${object}`); 37 | } 38 | 39 | fetch(`${credentials.url}/3/search/multi?api_key=${credentials.apikey}&query=${query}`) 40 | .then(response => response.json()) 41 | .then(body => { 42 | const data = body.results[0]; 43 | if (data) { 44 | state.action = extract(data); 45 | state.action.ms = (new Date() - ms); 46 | state.action.engine = 'themoviedb'; 47 | state.action.entity = entities.knowledge; 48 | } 49 | 50 | resolve(state); 51 | }); 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /src/actions/action.translator.js: -------------------------------------------------------------------------------- 1 | import countries from 'world-countries'; 2 | import Compromise from 'nlp_compromise'; 3 | import googleTranslate from 'google-translate-api'; 4 | import { entities } from '../helpers'; 5 | // -- Internal 6 | const DEMONYM = 'Demonym'; 7 | 8 | export default function(state) { 9 | return new Promise((resolve, reject) => { 10 | const actionIndex = state.tokens.indexOf('translate'); 11 | const terms = Compromise.text(state.sentence).sentences[0].terms; 12 | const ms = new Date(); 13 | let to; 14 | let demonymIndex; 15 | let sentence = ''; 16 | 17 | terms.forEach((term, index) => { 18 | if (index > actionIndex) { 19 | if (term.tag === DEMONYM && demonymIndex === undefined) { 20 | const demonym = term.text.toLowerCase(); 21 | const country = countries.find(item => item.demonym.toLowerCase() === demonym); 22 | 23 | if (country) { 24 | demonymIndex = index; 25 | to = country.cca2; 26 | } 27 | } else if (index > demonymIndex) { 28 | sentence += `${term.text} `; 29 | } 30 | } 31 | }); 32 | 33 | if (state.debug) { 34 | console.log('ActionTranslator'.bold.yellow, 'sentence:'.bold, sentence, 'to:'.bold, to); 35 | } 36 | 37 | if (sentence && to) { 38 | googleTranslate(sentence, { to }) 39 | .then(response => { 40 | state.action = { 41 | engine: 'google', 42 | ms: (new Date() - ms), 43 | entity: entities.knowledge, 44 | value: response.text, 45 | }; 46 | resolve(state); 47 | }) 48 | .catch(reject); 49 | } else { 50 | resolve(state); 51 | } 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /src/actions/action.wikipedia.js: -------------------------------------------------------------------------------- 1 | import wikipedia from 'wtf_wikipedia'; 2 | import { entities, relation } from '../helpers'; 3 | // -- Internal 4 | const RELATIONS = ['object', 'subject', 'location']; 5 | const DOCUMENT_TERMS = [ 6 | // -- Common 7 | 'image', 8 | 'website', 9 | // 'links', 10 | 'iso_code', 11 | // -- Person 12 | 'birth_place', 13 | 'occupation', 14 | // -- Places 15 | 'pushpin_map', 16 | 'coordinates_region', 17 | 'population_total', 18 | 'population_netro', 19 | 'area_total_km2', 20 | 'utc_offset_DST', 21 | ]; 22 | /* -- @TODO ------------------------------------------------------------------- 23 | - Use document.infobox_template 24 | - Try to get image 25 | ----------------------------------------------------------------------------- */ 26 | const extract = (properties = {}) => { 27 | const related = {}; 28 | 29 | DOCUMENT_TERMS.forEach((key) => { 30 | if (properties[key] && properties[key].text) related[key] = properties[key].text; 31 | }); 32 | 33 | return related; 34 | }; 35 | 36 | export default (state) => { 37 | const { object, subject, location } = relation(RELATIONS, state); 38 | const ms = new Date(); 39 | const concept = object || location || subject; 40 | 41 | return new Promise(resolve => { 42 | if (state.debug) { 43 | console.log('ActionWikipedia'.bold.yellow, `concept: ${concept}`); 44 | } 45 | 46 | if (!concept) resolve(state); 47 | 48 | wikipedia.from_api(concept, 'en', (response) => { 49 | const document = wikipedia.parse(response); 50 | if (document.type === 'page' && document.categories.length > 0) { 51 | const summary = document.text.Intro.map(sentence => sentence.text).join(' '); 52 | 53 | state.action = { 54 | ms: (new Date() - ms), 55 | engine: 'wikipedia', 56 | entity: entities.knowledge, 57 | image: `http://en.wikipedia.org/wiki/${document.images[0]}`, 58 | title: document.infobox.name ? document.infobox.name.text : concept, 59 | value: summary, 60 | related: extract(document.infobox), 61 | }; 62 | 63 | resolve(state); 64 | } 65 | }); 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | export { default as currency } from './action.currency'; 2 | export { default as forecastYahoo } from './action.forecast.yahoo'; 3 | export { default as forecastMSN } from './action.forecast.msn'; 4 | export { default as math } from './action.math'; 5 | export { default as movieDB } from './action.movie.themoviedb'; 6 | export { default as translator } from './action.translator'; 7 | export { default as wikipedia } from './action.wikipedia'; 8 | -------------------------------------------------------------------------------- /src/ava.js: -------------------------------------------------------------------------------- 1 | import pkg from '../package.json'; 2 | // -- Functions 3 | import intent from './intent'; 4 | import listen from './listen'; 5 | 6 | export default (props = {}) => { 7 | const state = { 8 | version: pkg.version, 9 | intents: [], 10 | debug: props.debug || false, 11 | }; 12 | 13 | return Object.assign( 14 | {}, 15 | intent(state), 16 | listen(state), 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/helpers/compose.js: -------------------------------------------------------------------------------- 1 | export default (...fns) => val => fns.reduce((acc, fn) => fn(acc), val); 2 | -------------------------------------------------------------------------------- /src/helpers/composeAsync.js: -------------------------------------------------------------------------------- 1 | export default (...fns) => state => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(state)); 2 | -------------------------------------------------------------------------------- /src/helpers/config.js: -------------------------------------------------------------------------------- 1 | // import path from 'path'; 2 | // const config = require(path.resolve('.', 'config.json')); 3 | 4 | let file; 5 | 6 | try { 7 | file = require(process.cwd() + '/config.json'); 8 | } catch (error) { 9 | file = {}; 10 | } 11 | 12 | export default (key) => file[key]; 13 | -------------------------------------------------------------------------------- /src/helpers/entities.js: -------------------------------------------------------------------------------- 1 | export default { 2 | request: 0, 3 | person: 1, 4 | location: 2, 5 | object: 3, 6 | knowledge: 4, 7 | number: 5, 8 | }; 9 | -------------------------------------------------------------------------------- /src/helpers/factoryActions.js: -------------------------------------------------------------------------------- 1 | export default async (state, actions) => { 2 | const promises = actions.map((action) => action.call(null, state)); 3 | 4 | return await Promise.race(promises); 5 | }; 6 | -------------------------------------------------------------------------------- /src/helpers/factoryIntents.js: -------------------------------------------------------------------------------- 1 | export default async (state) => { 2 | const intents = state.intents.map(intent => intent.script(state, intent.actions)); 3 | 4 | return await Promise.race(intents); 5 | }; 6 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as compose } from './compose'; 2 | export { default as composeAsync } from './composeAsync'; 3 | export { default as config } from './config'; 4 | export { default as entities } from './entities'; 5 | export { default as factoryActions } from './factoryActions'; 6 | export { default as factoryIntents } from './factoryIntents'; 7 | export { default as intersect } from './intersect'; 8 | export { default as relation } from './relation'; 9 | export { default as request } from './request'; 10 | export { default as resolve } from './resolve'; 11 | export { default as store } from './store'; 12 | export { default as syntax } from './syntax'; 13 | export { default as timeout } from './timeout'; 14 | -------------------------------------------------------------------------------- /src/helpers/intersect.js: -------------------------------------------------------------------------------- 1 | export default (array1 = [], array2 = []) => (array1.filter(item => array2.indexOf(item) !== -1).length > 0); 2 | -------------------------------------------------------------------------------- /src/helpers/relation.js: -------------------------------------------------------------------------------- 1 | export default (keys, state) => { 2 | const relations = state.relations || {}; 3 | const found = {}; 4 | keys.filter((key) => (relations[key] ? found[key] = relations[key].text : null)); 5 | 6 | return found; 7 | }; 8 | -------------------------------------------------------------------------------- /src/helpers/request.js: -------------------------------------------------------------------------------- 1 | import entities from './entities'; 2 | 3 | export default (state, request) => { 4 | state.action = { entity: entities.request, request }; 5 | 6 | return state; 7 | }; 8 | -------------------------------------------------------------------------------- /src/helpers/resolve.js: -------------------------------------------------------------------------------- 1 | export default (state) => new Promise(resolve => { 2 | if (state.intents && state.intents.length === 1) resolve(state); 3 | }); 4 | -------------------------------------------------------------------------------- /src/helpers/store.js: -------------------------------------------------------------------------------- 1 | // -- More info: https://github.com/typicode/lowdb 2 | import lowdb from 'lowdb'; 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | // -- Internal 6 | const folder = path.resolve('.', 'store'); 7 | 8 | if (!fs.existsSync(folder)) fs.mkdirSync(folder); 9 | 10 | export default (file, defaults = {}) => { 11 | const store = lowdb(`${folder}/${file}`, { storage: require('lowdb/lib/storages/file-async') }); 12 | 13 | if (defaults) store.defaults(defaults).value(); 14 | 15 | return store; 16 | }; 17 | -------------------------------------------------------------------------------- /src/helpers/syntax.js: -------------------------------------------------------------------------------- 1 | import Compromise from 'nlp_compromise'; 2 | 3 | export default (sentence, rules) => { 4 | if (!Array.isArray(rules)) rules = [rules]; 5 | let match; 6 | const rootSentence = Compromise.text(sentence).root(); 7 | 8 | for (const rule of rules) { 9 | const matches = Compromise.text(rootSentence).match(rule); 10 | 11 | if (matches.length > 0 && matches[0] !== null) { 12 | const values = {}; 13 | 14 | for (const term of matches[0].terms) { 15 | const key = term.tag.toLowerCase(); 16 | const text = key !== 'symbol' ? Compromise.text(term.text).root() : term.text; 17 | 18 | if (!values[key]) { 19 | values[key] = text; 20 | } else { 21 | if (!Array.isArray(values[key])) values[key] = [values[key]]; 22 | if (values[key].indexOf(text) === -1) { 23 | values[key].push(text); 24 | } 25 | } 26 | } 27 | 28 | match = Object.keys(values).length > 0 ? values : matches[0].text(); 29 | break; 30 | } 31 | } 32 | 33 | return match; 34 | }; 35 | -------------------------------------------------------------------------------- /src/helpers/timeout.js: -------------------------------------------------------------------------------- 1 | export default (reject, ms = 60000) => 2 | setTimeout(() => { 3 | reject(new Error(`Timeout after ${ms} ms`)); 4 | }, ms); 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Ava from './ava'; 2 | 3 | export default Ava; 4 | -------------------------------------------------------------------------------- /src/intent.js: -------------------------------------------------------------------------------- 1 | const isFunction = (value) => (typeof (value) === 'function'); 2 | 3 | export default (state) => ({ 4 | 5 | intent(script, actions) { 6 | if (isFunction(actions)) actions = [actions]; 7 | 8 | if (isFunction(script) && Array.isArray(actions)) { 9 | state.intents.push({ script, actions }); 10 | } 11 | 12 | return this; 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /src/intents/index.js: -------------------------------------------------------------------------------- 1 | export { default as any } from './intent.any'; 2 | export { default as conversor } from './intent.conversor'; 3 | export { default as movie } from './intent.movie'; 4 | export { default as translate } from './intent.translate'; 5 | export { default as weather } from './intent.weather'; 6 | -------------------------------------------------------------------------------- /src/intents/intent.any.js: -------------------------------------------------------------------------------- 1 | import { factoryActions } from '../helpers'; 2 | 3 | export default (state, actions) => { 4 | if (state.debug) console.log('IntentAny'.bold.green); 5 | 6 | return factoryActions(state, actions); 7 | }; 8 | -------------------------------------------------------------------------------- /src/intents/intent.conversor.js: -------------------------------------------------------------------------------- 1 | import { factoryActions, resolve, syntax } from '../helpers'; 2 | // -- Internal 3 | const RULES = [ 4 | 'how much is [value] [currency] [preposition] [currency]', 5 | 'convert [value] [currency] [preposition] [currency]', 6 | ]; 7 | 8 | export default (state, actions) => { 9 | const match = syntax(state.sentence, RULES); 10 | if (state.debug) console.log('IntentConversor'.bold.green, 'match:'.bold, match); 11 | 12 | return (match ? factoryActions(state, actions) : resolve(state)); 13 | }; 14 | -------------------------------------------------------------------------------- /src/intents/intent.movie.js: -------------------------------------------------------------------------------- 1 | import { factoryActions, intersect, resolve } from '../helpers'; 2 | // -- Internal 3 | const TERMS = [ 4 | 'film', 5 | 'movie', 6 | 'show', 7 | 'actor', 8 | 'director', 9 | 'camera', 10 | 'editor', 11 | 'cinema', 12 | 'tv', 13 | 'producer', 14 | ]; 15 | 16 | export default (state, actions) => { 17 | const tokens = intersect(TERMS, state.tokens); 18 | const classifiers = intersect(TERMS, state.classifier); 19 | if (state.debug) { 20 | console.log('IntentMovie'.bold.green, `tokens: ${tokens.toString().green}, classifiers: ${classifiers.toString().green}`); 21 | } 22 | 23 | return (tokens || classifiers) ? factoryActions(state, actions) : resolve(state); 24 | }; 25 | -------------------------------------------------------------------------------- /src/intents/intent.translate.js: -------------------------------------------------------------------------------- 1 | import { factoryActions, resolve, syntax } from '../helpers'; 2 | // -- Internal 3 | const RULES = [ 4 | 'translate [Preposition]? [Demonym]', 5 | 'translate * [Preposition] [Demonym]', 6 | ]; 7 | 8 | export default (state, actions) => { 9 | const match = syntax(state.sentence, RULES); 10 | if (state.debug) console.log('IntentTranslate'.bold.green, 'match:'.bold, match); 11 | 12 | return (match ? factoryActions(state, actions) : resolve(state)); 13 | }; 14 | -------------------------------------------------------------------------------- /src/intents/intent.weather.js: -------------------------------------------------------------------------------- 1 | import { factoryActions, intersect, resolve } from '../helpers'; 2 | // -- Internal 3 | const TERMS = [ 4 | 'weather', 5 | 'umbrella', 6 | 'rain', 7 | 'forecast', 8 | 'snow', 9 | 'fog', 10 | 'sun', 11 | 'cloud', 12 | 'meteo', 13 | ]; 14 | 15 | export default (state, actions) => { 16 | const tokens = intersect(TERMS, state.tokens); 17 | const classifiers = intersect(TERMS, state.classifier); 18 | if (state.debug) { 19 | console.log('IntentWeather'.bold.green, `tokens: ${tokens.toString().green}, classifiers: ${classifiers.toString().green}`); 20 | } 21 | 22 | return (tokens || classifiers) ? factoryActions(state, actions) : resolve(state); 23 | }; 24 | -------------------------------------------------------------------------------- /src/listen.js: -------------------------------------------------------------------------------- 1 | import { composeAsync, factoryIntents, timeout } from './helpers'; 2 | import factoryProcessor from './processor'; 3 | 4 | export default (state) => ({ 5 | listen(sentence, ms) { 6 | return new Promise((resolve, reject) => { 7 | state.rawSentence = sentence; 8 | 9 | if (ms) timeout(reject, ms); 10 | const factory = composeAsync(factoryProcessor, factoryIntents); 11 | 12 | factory(state) 13 | .then(() => { 14 | if (state.action) { 15 | resolve(state); 16 | } else { 17 | reject(new Error('Unknown action')); 18 | } 19 | }) 20 | .catch(error => { 21 | if (!error) error = { code: 0, message: "Sorry, I haven't understood you" }; 22 | reject(error); 23 | }); 24 | }); 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /src/processor/classifier.js: -------------------------------------------------------------------------------- 1 | // -- More info: https://github.com/ttezel/bayes 2 | import bayes from 'bayes'; 3 | import { store } from '../helpers'; 4 | 5 | // -- Internal 6 | const db = store('classifier.json'); 7 | const classifierForLanguage = (language) => { 8 | const value = db.get(language).value(); 9 | return (value ? bayes.fromJson(value) : bayes()); 10 | }; 11 | 12 | export default (state) => { 13 | const classifier = classifierForLanguage(state.language); 14 | let categories = classifier.categorize(state.rawSentence); 15 | 16 | if (state.taxonomy && state.taxonomy !== categories) { 17 | classifier.learn(state.rawSentence, state.taxonomy); 18 | db.set(state.language, classifier.toJson()).value(); 19 | categories = state.taxonomy; 20 | } 21 | state.classifier = categories ? categories.split('/') : []; 22 | 23 | return state; 24 | }; 25 | -------------------------------------------------------------------------------- /src/processor/compromise.js: -------------------------------------------------------------------------------- 1 | import Compromise from 'nlp_compromise'; 2 | 3 | export default (state) => { 4 | const compromise = Compromise.text(state.sentence); 5 | 6 | state.type = Compromise.sentence(state.sentence).sentence_type(); 7 | state.topics = compromise.topics().map(topic => topic.text); 8 | state.tags = compromise.tags()[0]; 9 | 10 | return (state); 11 | }; 12 | -------------------------------------------------------------------------------- /src/processor/index.js: -------------------------------------------------------------------------------- 1 | import { composeAsync } from '../helpers'; 2 | 3 | import language from './language'; 4 | import translator from './translator'; 5 | import classifier from './classifier'; 6 | import compromise from './compromise'; 7 | import taxonomy from './taxonomy'; 8 | import tokens from './tokens'; 9 | import relations from './relations'; 10 | import sentiment from './sentiment'; 11 | 12 | export default (state) => { 13 | const factory = composeAsync(language, translator, taxonomy, classifier, compromise, tokens, relations, sentiment); 14 | 15 | return factory(state); 16 | }; 17 | -------------------------------------------------------------------------------- /src/processor/language.js: -------------------------------------------------------------------------------- 1 | import cld from 'cld'; 2 | 3 | export default function(state) { 4 | return new Promise((resolve) => { 5 | cld.detect(state.rawSentence, (error, value) => { 6 | if (!error) { 7 | state.language = value.languages[0].code; 8 | } 9 | state.sentence = state.rawSentence; 10 | 11 | resolve(state); 12 | }); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /src/processor/relations.js: -------------------------------------------------------------------------------- 1 | import Compromise from 'nlp_compromise'; 2 | import Chrono from 'chrono-node'; 3 | // -- Internal 4 | const TERMS_RELATIONS = { 5 | person: 'subject', 6 | adverb: 'adverb', 7 | verb: 'action', 8 | noun: 'object', 9 | date: 'when', 10 | place: 'location', 11 | city: 'location', 12 | value: 'value', 13 | }; 14 | const lexicon = Compromise.lexicon(); 15 | lexicon.ava = 'Person'; 16 | lexicon.here = 'Place'; 17 | 18 | // -- Private methods 19 | const extractRelation = (tag, term, previous) => { 20 | const relation = {}; 21 | let text = term.normal || term.text; 22 | 23 | switch (tag) { 24 | case 'date': { 25 | text = Chrono.parseDate(text); 26 | break; 27 | } 28 | 29 | case 'verb': { 30 | const compromiseVerb = Compromise.verb(term.expansion || term.text); 31 | if (!previous) { 32 | relation.verb = { 33 | tense: compromiseVerb.conjugation().toLowerCase().split('tense')[0], 34 | negative: compromiseVerb.isNegative(), 35 | }; 36 | } else { 37 | relation.verb = previous.verb; 38 | } 39 | text = compromiseVerb.conjugate().infinitive; 40 | break; 41 | } 42 | 43 | case 'noun': { 44 | text = Compromise.text(text).root(); 45 | break; 46 | } 47 | 48 | case 'person': { 49 | tag = term.pos.Pronoun ? 'pronoun' : tag; 50 | break; 51 | } 52 | 53 | default: 54 | break; 55 | } 56 | 57 | relation.tag = tag; 58 | relation.text = text; 59 | 60 | return relation; 61 | }; 62 | 63 | 64 | export default (state) => { 65 | const sentence = state.sentence || Compromise.text(state.sentence).normal(); 66 | const compromiseSentences = Compromise.text(sentence, { lexicon }).sentences; 67 | const terms = (compromiseSentences[0]) ? compromiseSentences[0].terms : []; 68 | const relations = {}; 69 | 70 | terms.forEach(term => { 71 | const tag = (term.pos.Verb ? 'verb' : term.tag).toLowerCase(); 72 | let relation = TERMS_RELATIONS[tag]; 73 | 74 | if (relation) { 75 | if (relation === TERMS_RELATIONS.person && relations[relation]) { 76 | relation = (relations[relation] !== (term.normal || term.text)) ? TERMS_RELATIONS.noun : undefined; 77 | } 78 | if (relation) relations[relation] = extractRelation(tag, term, relations[relation]); 79 | } 80 | }); 81 | state.relations = relations; 82 | 83 | return (state); 84 | }; 85 | -------------------------------------------------------------------------------- /src/processor/sentiment.js: -------------------------------------------------------------------------------- 1 | import sentiment from 'sentiment'; 2 | 3 | export default (state) => { 4 | const sentence = sentiment(state.sentence.toLowerCase()); 5 | state.sentiment = sentence.score; 6 | 7 | return state; 8 | }; 9 | -------------------------------------------------------------------------------- /src/processor/taxonomy.js: -------------------------------------------------------------------------------- 1 | import AlchemyAPI from 'alchemy-api'; 2 | import { config } from '../helpers'; 3 | // -- Internal 4 | const credentials = config('alchemy'); 5 | let processor; 6 | if (credentials) processor = new AlchemyAPI(credentials.apikey); 7 | 8 | export default (state) => { 9 | if (!credentials) return (state); 10 | 11 | return new Promise((resolve) => { 12 | processor.taxonomies(state.sentence, {}, (error, response) => { 13 | if (error || !response.taxonomy) return resolve(state); 14 | 15 | const taxonomy = response.taxonomy[0]; 16 | if (taxonomy) { 17 | state.taxonomy = (taxonomy.label.charAt(0) === '/') ? taxonomy.label.slice(1) : taxonomy.label; 18 | } 19 | 20 | return resolve(state); 21 | }); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /src/processor/tokens.js: -------------------------------------------------------------------------------- 1 | import Compromise from 'nlp_compromise'; 2 | 3 | export default (state) => { 4 | state.tokens = Compromise.text(state.sentence).root().split(' '); 5 | 6 | return (state); 7 | }; 8 | -------------------------------------------------------------------------------- /src/processor/translator.js: -------------------------------------------------------------------------------- 1 | import googleTranslate from 'google-translate-api'; 2 | // -- Internal 3 | const LANGUAGE = 'en'; 4 | 5 | export default function(state) { 6 | return new Promise((resolve, reject) => { 7 | if (state.language === LANGUAGE) return resolve(state); 8 | 9 | return googleTranslate(state.rawSentence, { from: state.language, to: LANGUAGE }) 10 | .then(response => { 11 | state.language = response.from.language.iso; 12 | state.sentence = response.text; 13 | resolve(state); 14 | }) 15 | .catch(error => { 16 | reject(error); 17 | }); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /test/actions/action.currency.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { currency } from '../../src/actions'; 10 | 11 | describe('Action: currency', () => { 12 | 13 | let state = {}; 14 | beforeEach( () => { 15 | state.action = undefined; 16 | }); 17 | 18 | it('Up & Running', async () => { 19 | expect(currency).to.be.ok; 20 | }); 21 | 22 | it('Detected using name of currency', async () => { 23 | const quantity = 10; 24 | state.sentence = `convert ${quantity} euros into dollars`; 25 | await currency(state); 26 | 27 | expect(state.action).to.be.ok; 28 | expect(state.action.value).to.be.above(quantity); 29 | }); 30 | 31 | it('Detected using acronym of currency', async () => { 32 | const quantity = 10; 33 | state.sentence = `convert ${quantity} eur into usd`; 34 | await currency(state); 35 | 36 | expect(state.action).to.be.ok; 37 | expect(state.action.value).to.be.above(quantity); 38 | }); 39 | 40 | it('Not Detected using symbol of currency', async () => { 41 | const quantity = 10; 42 | state.sentence = `convert ${quantity} € into $`; 43 | 44 | expect( currency(state) ).to.be.rejected; 45 | }); 46 | 47 | it('Not detected', async () => { 48 | state.sentence = 'Hello world'; 49 | 50 | expect( currency(state) ).to.be.rejected; 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/actions/action.forecast.msn.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { forecastMSN } from '../../src/actions'; 10 | 11 | describe('Action: forecastMSN', () => { 12 | 13 | let state = {}; 14 | beforeEach( () => { 15 | state.action = undefined; 16 | state.relations = { 17 | location: {text: 'madrid'}, 18 | when: {text: new Date()} 19 | } 20 | }); 21 | 22 | it('Up & Running', async () => { 23 | expect(forecastMSN).to.be.ok; 24 | }); 25 | 26 | it('Detected using relations location & when', async () => { 27 | await forecastMSN(state); 28 | 29 | expect(state.action).to.be.ok; 30 | expect(state.action).to.have.all.keys('ms', 'engine', 'entity', 'title', 'value'); 31 | expect(state.action.value).to.have.all.keys('code', 'condition', 'temperature', 'date'); 32 | }); 33 | 34 | it('Detected using relation location (forecast mode)', async () => { 35 | state.relations.when = undefined; 36 | await forecastMSN(state); 37 | 38 | expect(state.action.related).to.be.ok; 39 | }); 40 | 41 | it('Not detected', async () => { 42 | state.relations = undefined; 43 | expect( forecastMSN(state) ).to.be.rejected; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/actions/action.forecastYahoo.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { forecastYahoo } from '../../src/actions'; 10 | 11 | describe('Action: forecastYahoo', () => { 12 | 13 | let state = {}; 14 | beforeEach( () => { 15 | state.action = undefined; 16 | state.relations = { 17 | location: {text: 'madrid'}, 18 | when: {text: new Date()} 19 | } 20 | }); 21 | 22 | it('Up & Running', async () => { 23 | expect(forecastYahoo).to.be.ok; 24 | }); 25 | 26 | it('Detected using relations location & when', async () => { 27 | await forecastYahoo(state); 28 | 29 | expect(state.action).to.be.ok; 30 | expect(state.action).to.have.all.keys('ms', 'engine', 'entity', 'title', 'url', 'value'); 31 | expect(state.action.value).to.have.all.keys('code', 'condition', 'temperature', 'date'); 32 | }); 33 | 34 | it('Detected using relation location (forecast mode)', async () => { 35 | state.relations.when = undefined; 36 | await forecastYahoo(state); 37 | 38 | expect(state.action.related).to.be.ok; 39 | }); 40 | 41 | it('Not detected', async () => { 42 | state.relations = undefined; 43 | expect( forecastYahoo(state) ).to.be.rejected; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/actions/action.math.spec.js: -------------------------------------------------------------------------------- 1 | /* @TODO: 2 | - Use operators + - * / 3 | - Detect modulus (%) operations 4 | - Improve matching sentence 5 | */ 6 | 'use strict'; 7 | 8 | import { expect } from 'chai'; 9 | import chai from 'chai'; 10 | import chaiAsPromised from 'chai-as-promised'; 11 | chai.use(chaiAsPromised); 12 | chai.should(); 13 | 14 | import { math } from '../../src/actions'; 15 | 16 | describe('Action: math', () => { 17 | 18 | let state = {}; 19 | const a = 80; 20 | const b = 83; 21 | beforeEach( () => { 22 | state.action = undefined; 23 | }); 24 | 25 | it('Up & Running', async () => { 26 | expect(math).to.be.ok; 27 | }); 28 | 29 | it('Detect a addition (+) operation', () => { 30 | state.sentence = `How much is ${a} plus ${b}`; 31 | math(state); 32 | expect(state.action.value).to.be.equal(a + b); 33 | 34 | state.sentence = `How much is ${a}+${b}`; 35 | math(state); 36 | expect(state.action.value).to.be.equal(a + b); 37 | 38 | state.sentence = `add ${a} to ${b}`; 39 | math(state); 40 | expect(state.action.value).to.be.equal(a + b) 41 | }); 42 | 43 | it('Detect a subtraction (-) operation', () => { 44 | state.sentence = `How much is ${a} minus ${b}`; 45 | math(state); 46 | expect(state.action.value).to.be.equal(a - b) 47 | 48 | state.sentence = `How much is ${a}-${b}`; 49 | math(state); 50 | expect(state.action.value).to.be.equal(a - b); 51 | 52 | state.sentence = `subtract ${a} to ${b}`; 53 | math(state); 54 | expect(state.action.value).to.be.equal(a - b); 55 | }); 56 | 57 | it('Detect a multiplication (*) operation', () => { 58 | state.sentence = `How much is ${a} multiplied by ${b}`; 59 | math(state); 60 | expect(state.action.value).to.be.equal(a * b) 61 | 62 | state.sentence = `How much is ${a}*${b}`; 63 | math(state); 64 | expect(state.action.value).to.be.equal(a * b); 65 | }); 66 | 67 | it('Detect a division (/) operation', () => { 68 | state.sentence = `How much is ${a} divided by ${b}`; 69 | math(state); 70 | expect(state.action.value).to.be.equal(a / b) 71 | 72 | state.sentence = `How much is ${a}/${b}`; 73 | math(state); 74 | expect(state.action.value).to.be.equal(a / b); 75 | }); 76 | 77 | it('Not detected', async () => { 78 | state.sentence = 'Hello world'; 79 | expect( math(state) ).to.be.rejected; 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/actions/action.mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { entities } from '../../src/helpers' 4 | 5 | export default (state) => { 6 | 7 | return new Promise(async (resolve, reject) => { 8 | setTimeout(() => { 9 | state.action = { 10 | engine: 'mock', 11 | ms: 10, 12 | type: entities.knowledge 13 | }; 14 | 15 | resolve(state); 16 | }, 10) 17 | }) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /test/actions/action.movie.themoviedb.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { movieDB } from '../../src/actions'; 10 | 11 | describe('Action: movieDB', () => { 12 | 13 | let state = {}; 14 | beforeEach( () => { 15 | state.action = undefined; 16 | state.relations = { 17 | subject: {text: 'Leonardo Dicaprio'} 18 | } 19 | }); 20 | 21 | it('Up & Running', async () => { 22 | expect(movieDB).to.be.ok; 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/actions/action.translator.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { translator } from '../../src/actions'; 10 | 11 | describe('Action: translator', () => { 12 | 13 | let state = {}; 14 | beforeEach( () => { 15 | state.action = undefined; 16 | state.tokens = ['translate']; 17 | state.sentence = undefined; 18 | }); 19 | 20 | it('Up & Running', async () => { 21 | expect(translator).to.be.ok; 22 | }); 23 | 24 | it("Detected using 'translate into...'", async () => { 25 | state.sentence = 'How can I translate into italian i have hungry'; 26 | await translator(state); 27 | 28 | expect(state.action).to.be.ok; 29 | expect(state.action.value.toLowerCase()).to.be.equal('ho fame'); 30 | }); 31 | 32 | it("Detected using 'how can i translate in...'", async () => { 33 | state.sentence = "How can I translate in spanish i'm hungry"; 34 | await translator(state); 35 | 36 | expect(state.action).to.be.ok; 37 | expect(state.action.value.toLowerCase()).to.be.equal('tengo hambre'); 38 | }); 39 | 40 | it('Not detected', async () => { 41 | state.sentence = 'Hello world'; 42 | expect( translator(state) ).to.be.rejected; 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/actions/action.wikipedia.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { wikipedia } from '../../src/actions'; 10 | 11 | describe('Action: wikipedia', () => { 12 | 13 | let state = {}; 14 | beforeEach( () => { 15 | state.action = undefined; 16 | state.relations = { 17 | subject: {text: 'Leonardo Dicaprio'} 18 | } 19 | }); 20 | 21 | it('Up & Running', async () => { 22 | expect(wikipedia).to.be.ok; 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/ava.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { assert, expect } from 'chai'; 4 | import Ava from '../src'; 5 | 6 | describe('Ava', function() { 7 | 8 | let ava; 9 | beforeEach( () => ava = new Ava() ); 10 | 11 | it('Up & Running', function () { 12 | const methods = Object.keys(ava); 13 | 14 | expect(typeof(ava)).to.equal('object'); 15 | 16 | expect(methods.length).to.equal(2); 17 | 18 | expect(methods[0]).to.equal('intent'); 19 | expect(typeof(ava.intent)).to.equal('function'); 20 | 21 | expect(methods[1]).to.equal('listen'); 22 | expect(typeof(ava.listen)).to.equal('function'); 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/helpers/compose.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { compose } from '../../src/helpers'; 5 | 6 | describe('Helper: compose', () => { 7 | 8 | it('Up & Running', async () => { 9 | expect(compose).to.be.ok; 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/helpers/composeAsync.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { composeAsync } from '../../src/helpers'; 5 | 6 | describe('Helper: composeAsync', () => { 7 | 8 | it('Up & Running', async () => { 9 | expect(composeAsync).to.be.ok; 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/helpers/entities.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { entities } from '../../src/helpers'; 5 | 6 | describe('Helper: entities', () => { 7 | 8 | it('Up & Running', async () => { 9 | expect(entities).to.be.ok; 10 | }); 11 | 12 | it('Detected a simple matching sentence', () => { 13 | expect(entities).to.have.all.keys('request', 'person', 'location', 'object', 'knowledge', 'number'); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /test/helpers/factoryActions.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { factoryActions } from '../../src/helpers'; 5 | 6 | describe('Helper: factoryActions', () => { 7 | 8 | it('Up & Running', async () => { 9 | expect(factoryActions).to.be.ok; 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/helpers/factoryIntents.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { factoryIntents } from '../../src/helpers'; 5 | 6 | describe('Helper: factoryIntents', () => { 7 | 8 | it('Up & Running', async () => { 9 | expect(factoryIntents).to.be.ok; 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/helpers/intersect.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { intersect } from '../../src/helpers'; 5 | 6 | describe('Helper: intersect', () => { 7 | 8 | let terms = ['film', 'movie', 'cinema']; 9 | let state = {}; 10 | beforeEach( () => { 11 | state.tokens = ['i', 'wanna', 'go', 'to', 'cinema', 'this', 'evening']; 12 | }); 13 | 14 | it('Up & Running', async () => { 15 | expect(intersect).to.be.ok; 16 | }); 17 | 18 | it('Detected an array intersection', async () => { 19 | const value = intersect(terms, state.tokens); 20 | expect(value).to.be.true; 21 | }); 22 | 23 | it('Not detected', async () => { 24 | state.tokens = ['i', 'wanna', 'go', 'to', 'mcdonalds', 'this', 'evening']; 25 | const value = intersect(terms, state.tokens); 26 | expect(value).to.be.false; 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/helpers/relation.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { relation } from '../../src/helpers'; 5 | 6 | describe('Helper: relation', () => { 7 | 8 | let state = {}; 9 | beforeEach( () => { 10 | state.relations = { 11 | subject: { text: 'ava' }, 12 | action: { text: 'help' }, 13 | location: { text: 'london' } 14 | } 15 | }); 16 | 17 | it('Up & Running', async () => { 18 | expect(relation).to.be.ok; 19 | }); 20 | 21 | it('Extract relations attributes', async () => { 22 | const { subject, location } = relation(['subject', 'location'], state); 23 | expect(subject).to.be.equal('ava'); 24 | expect(location).to.be.equal('london'); 25 | }); 26 | 27 | it('cannot extract a specific relation', async () => { 28 | const { action, when } = relation(['action', 'when'], state); 29 | expect(action).to.be.equal('help'); 30 | expect(when).not.to.be.ok; 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/helpers/request.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { request } from '../../src/helpers'; 5 | 6 | describe('Helper: request', () => { 7 | 8 | let state = {}; 9 | beforeEach( () => { 10 | state = {}; 11 | }); 12 | 13 | it('Up & Running', async () => { 14 | expect(request).to.be.ok; 15 | }); 16 | 17 | it('Creates a request with an specific relation', async () => { 18 | const parameter = {relation: 'location'}; 19 | request(state, parameter); 20 | expect(state.action).to.be.ok; 21 | expect(state.action.request).to.be.equal(parameter); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/helpers/resolve.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { resolve } from '../../src/helpers'; 5 | import { any } from '../../src/intents'; 6 | 7 | describe('Helper: resolve', () => { 8 | 9 | let state = {}; 10 | beforeEach( () => { 11 | state = {}; 12 | }); 13 | 14 | it('Up & Running', async () => { 15 | expect(resolve).to.be.ok; 16 | }); 17 | 18 | it('Finish the process if only have 1 intent', async () => { 19 | state.intents = [any]; 20 | await resolve(state); 21 | expect(state).to.be.ok; 22 | }); 23 | 24 | it('If dont have any intent wait', async () => { 25 | expect( resolve(state) ).to.be.rejected; 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/helpers/store.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { store } from '../../src/helpers'; 5 | 6 | describe('Helper: store', () => { 7 | 8 | it('Up & Running', async () => { 9 | expect(store).to.be.ok; 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/helpers/syntax.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { syntax } from '../../src/helpers'; 5 | 6 | describe('Helper: syntax', () => { 7 | 8 | let sentence = 'how much is 20 euros in dollars?'; 9 | 10 | it('Up & Running', async () => { 11 | expect(syntax).to.be.ok; 12 | }); 13 | 14 | it('Detected a simple matching sentence', () => { 15 | const state = syntax(sentence, '20 [currency] in [currency]'); 16 | expect(state).to.be.ok; 17 | expect(state.value).to.equal('20'); 18 | expect(state.currency[0]).to.equal('euro'); 19 | expect(state.currency[1]).to.equal('dollar'); 20 | }); 21 | 22 | it('Detected a complex matching sentence', () => { 23 | const state = syntax(sentence, '[value] [currency] [preposition] [currency]'); 24 | 25 | expect(state).to.be.ok; 26 | expect(state).to.have.all.keys('value', 'currency', 'preposition'); 27 | expect(state.value).to.equal('20'); 28 | expect(state.currency[0]).to.equal('euro'); 29 | expect(state.currency[1]).to.equal('dollar'); 30 | expect(state.preposition).to.equal('in'); 31 | }); 32 | 33 | it('Undetect a matching sentence', () => { 34 | const state = syntax(sentence, '[currency] [value]'); 35 | expect(state).not.to.be.ok; 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/helpers/timeout.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import { timeout } from '../../src/helpers'; 5 | 6 | describe('Helper: timeout', () => { 7 | 8 | it('Up & Running', async () => { 9 | expect(timeout).to.be.ok; 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/intent.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { assert, expect, should } from 'chai'; 4 | import Ava from '../src'; 5 | import { any } from '../src/intents'; 6 | import actionMock from './actions/action.mock'; 7 | 8 | 9 | describe('.intent()', () => { 10 | 11 | let ava; 12 | beforeEach( () => ava = new Ava() ); 13 | 14 | it('Up and running', () => { 15 | expect(ava.intent).to.be.ok; 16 | }); 17 | 18 | it('can add a new instance to Ava instance', () => { 19 | const state = ava.intent(any, actionMock); 20 | 21 | expect(ava.intent(any, actionMock)).to.be.ok; 22 | }); 23 | 24 | it('method may be chainable', () => { 25 | const instance = ava.intent(any, actionMock); 26 | 27 | expect(instance).to.have.all.keys('intent', 'listen'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/intents/intent.any.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | 5 | import { any } from '../../src/intents'; 6 | import ActionMock from '../actions/action.mock' 7 | 8 | describe('IntentAny', () => { 9 | 10 | let state = {}; 11 | beforeEach( () => { 12 | state.action = undefined; 13 | }); 14 | 15 | it('Up & Running', async () => { 16 | expect(any).to.be.ok; 17 | }); 18 | 19 | it('Detected with any condition', async () => { 20 | await any(state, [ActionMock]); 21 | 22 | expect(state.action).to.be.ok; 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/intents/intent.conversor.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { conversor } from '../../src/intents'; 10 | import ActionMock from '../actions/action.mock' 11 | 12 | describe('IntentConversor', () => { 13 | 14 | let state = {}; 15 | beforeEach( () => { 16 | state.action = undefined; 17 | state.sentence = 'how much is 20 euros in dollars?'; 18 | }); 19 | 20 | it('Up & Running', async () => { 21 | expect(conversor).to.be.ok; 22 | }); 23 | 24 | it("Detected with rule 'how much is...'", async () => { 25 | await conversor(state, [ActionMock]); 26 | expect(state.action).to.be.ok; 27 | }); 28 | 29 | it("Detected with rule 'convert...'", async () => { 30 | // -- @TODO: 👻 'Ava I need convert 20 dollars into euros' is not detected. 31 | state.sentence = 'Ava, can you convert 20 dollars into euros' 32 | await conversor(state, [ActionMock]); 33 | expect(state.action).to.be.ok; 34 | }); 35 | 36 | it('Not detected', async () => { 37 | state.sentence = 'Hello World!'; 38 | expect( conversor(state, [ActionMock]) ).to.be.rejected; 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/intents/intent.movie.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { movie } from '../../src/intents'; 10 | import ActionMock from '../actions/action.mock' 11 | 12 | describe('IntentMovie', () => { 13 | 14 | let state = {}; 15 | beforeEach( () => { 16 | state.action = undefined; 17 | state.classifier = []; 18 | state.tokens = ['i', 'want', 'go', 'to', 'cinema'] 19 | }); 20 | 21 | it('Up & Running', async () => { 22 | expect(movie).to.be.ok; 23 | }); 24 | 25 | it('Detected with {tokens}', async () => { 26 | await movie(state, [ActionMock]); 27 | 28 | expect(state.action).to.be.ok; 29 | }); 30 | 31 | it('Detected with {classifier}', async () => { 32 | state.tokens = []; 33 | state.classifier = ['cinema'] 34 | await movie(state, [ActionMock]); 35 | 36 | expect(state.action).to.be.ok; 37 | }); 38 | 39 | it('Not detected', async () => { 40 | state.tokens = []; 41 | state.classifier = []; 42 | expect( movie(state, [ActionMock]) ).to.be.rejected; 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/intents/intent.translate.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { translate } from '../../src/intents'; 10 | import ActionMock from '../actions/action.mock' 11 | 12 | describe('IntentTranslate', () => { 13 | 14 | let state = {}; 15 | beforeEach( () => { 16 | state.action = undefined; 17 | }); 18 | 19 | it('Up & Running', async () => { 20 | expect(translate).to.be.ok; 21 | }); 22 | 23 | it('Detected with {syntax} (rule n1) ', async () => { 24 | state.sentence = 'How can I translate into italian i have hungry'; 25 | await translate(state, [ActionMock]); 26 | expect(state.action).to.be.ok; 27 | }); 28 | 29 | it('Detected with {syntax} (rule n1) and gerund verb', async () => { 30 | state.sentence = 'can you help me translating into spanish hello i am javi'; 31 | await translate(state, [ActionMock]); 32 | expect(state.action).to.be.ok; 33 | }); 34 | 35 | // -- @TODO: 👻 when compromise fix the syntax matching --------------------- 36 | // it('Detected with {syntax} (rule n2) ', async () => { 37 | // state.sentence = 'Ava, how can translate hello world in thai'; 38 | // await translate(state, [ActionMock]); 39 | // expect(state.action).to.be.ok; 40 | // }); 41 | // --------------------------------------------------------------------------- 42 | 43 | it('Not detected', async () => { 44 | state.sentence = 'Hello world!'; 45 | expect( translate(state, [ActionMock]) ).to.be.rejected; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/intents/intent.weather.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import chai from 'chai'; 5 | import chaiAsPromised from 'chai-as-promised'; 6 | chai.use(chaiAsPromised); 7 | chai.should(); 8 | 9 | import { weather } from '../../src/intents'; 10 | import ActionMock from '../actions/action.mock' 11 | 12 | describe('IntentWeather', () => { 13 | 14 | let state = {}; 15 | beforeEach( () => { 16 | state.action = undefined; 17 | state.classifier = []; 18 | state.tokens = ['will', 'rain', 'tomorrow', 'in', 'london'] 19 | }); 20 | 21 | it('Up & Running', async () => { 22 | expect(weather).to.be.ok; 23 | }); 24 | 25 | it('Detected with {tokens}', async () => { 26 | await weather(state, [ActionMock]); 27 | 28 | expect(state.action).to.be.ok; 29 | }); 30 | 31 | it('Detected with {classifier}', async () => { 32 | state.tokens = []; 33 | state.classifier = ['rain'] 34 | await weather(state, [ActionMock]); 35 | 36 | expect(state.action).to.be.ok; 37 | }); 38 | 39 | it('Not detected', async () => { 40 | state.tokens = []; 41 | state.classifier = []; 42 | expect( weather(state, [ActionMock]) ).to.be.rejected; 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /test/listen.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import pkg from '../package.json'; 4 | import { expect } from 'chai'; 5 | import chai from 'chai'; 6 | import chaiAsPromised from 'chai-as-promised'; 7 | chai.use(chaiAsPromised); 8 | chai.should(); 9 | 10 | import Ava from '../src'; 11 | import { any, weather } from '../src/intents' 12 | import actionMock from './actions/action.mock' 13 | 14 | describe('.listen()', () => { 15 | 16 | let ava; 17 | beforeEach( () => ava = new Ava() ); 18 | 19 | it('Up and running', () => { 20 | expect(ava.listen).to.be.ok; 21 | }); 22 | 23 | it('Returns a Promise (then/catch)', () => { 24 | const listen = ava.listen(); 25 | 26 | expect(listen instanceof Promise).to.equal(true); 27 | expect(typeof(listen.then)).to.equal('function'); 28 | expect(typeof(listen.catch)).to.equal('function'); 29 | }); 30 | 31 | it('Is successful', async () => { 32 | ava.intent(any, actionMock); 33 | const state = await ava.listen('Hello world!'); 34 | 35 | expect(state.version).to.equal(pkg.version); 36 | expect(state.intents.length).to.equal(1); 37 | expect(state.action).to.exist; 38 | }); 39 | 40 | it('Listen is unsuccesful because no intent is matched', () => { 41 | ava.intent(weather, actionMock); 42 | 43 | expect( ava.listen('Hello world!') ).to.be.rejected; 44 | }); 45 | 46 | it('Listen is unsuccesful because timeout', () => { 47 | ava.intent(any, []); 48 | const timeout = 300; 49 | 50 | expect( ava.listen('Hello world!', timeout) ).to.be.rejected; 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /test/processor/classifier.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import classifier from '../../src/processor/classifier'; 5 | 6 | describe('Processor: classifier', () => { 7 | 8 | let state; 9 | beforeEach( () => state = { rawSentence: 'Hello world!', language: 'en' } ); 10 | 11 | it('Up & Running', () => { 12 | expect(classifier).to.be.ok; 13 | }); 14 | 15 | it('Compose property {classifier}', () => { 16 | classifier(state) 17 | expect(state).to.have.all.keys('rawSentence', 'language', 'classifier'); 18 | }); 19 | 20 | it('Learn using a taxonomy', () => { 21 | state.taxonomy = 'greetings/developers'; 22 | classifier(state); 23 | 24 | expect(state.classifier.length).equal(2) 25 | expect(state.classifier[0]).equal('greetings'); 26 | expect(state.classifier[1]).equal('developers'); 27 | }); 28 | 29 | it('Categorize without taxonomy', () => { 30 | classifier(state); 31 | 32 | expect(state.classifier.length).equal(2) 33 | expect(state.classifier[0]).equal('greetings'); 34 | expect(state.classifier[1]).equal('developers'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/processor/compromise.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import compromise from '../../src/processor/compromise'; 5 | 6 | describe('Processor: compromise', () => { 7 | 8 | let state; 9 | beforeEach( () => state = {}) 10 | 11 | it('Up & Running', () => { 12 | expect(compromise).to.be.ok; 13 | }); 14 | 15 | it('Compose properties {type, topics}', () => { 16 | state.sentence = 'Hello world'; 17 | compromise(state); 18 | expect(state).to.have.all.keys('sentence', 'type', 'topics', 'tags'); 19 | }); 20 | 21 | it('Detect topics in a sentence', () => { 22 | state.sentence = 'I will visit London and Madrid next week'; 23 | compromise(state); 24 | expect(state.topics.length).equal(2); 25 | expect(state.topics[0]).equal('london'); 26 | expect(state.topics[1]).equal('madrid'); 27 | }); 28 | 29 | it('Detect tags in a sentence', () => { 30 | state.sentence = 'I will visit London and Madrid next week'; 31 | compromise(state); 32 | expect(state.tags.length).equal(6); 33 | expect(state.tags[0]).equal('Person'); 34 | expect(state.tags[1]).equal('FutureTense'); 35 | expect(state.tags[2]).equal('City'); 36 | expect(state.tags[3]).equal('Conjunction'); 37 | expect(state.tags[4]).equal('City'); 38 | expect(state.tags[5]).equal('Date'); 39 | }); 40 | 41 | it('Detect an declarative sentence', () => { 42 | state.sentence = 'Hello world'; 43 | compromise(state); 44 | expect(state.type).equal('declarative'); 45 | }); 46 | 47 | it('Detect an interrogative sentence', () => { 48 | state.sentence = 'Where you wanna go tonight?'; 49 | compromise(state); 50 | expect(state.type).equal('interrogative'); 51 | }); 52 | 53 | it('Detect an exclamative sentence', () => { 54 | state.sentence = 'I hate you!'; 55 | compromise(state); 56 | expect(state.type).equal('exclamative'); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /test/processor/language.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import language from '../../src/processor/language'; 5 | 6 | describe('Processor: language', () => { 7 | 8 | let state; 9 | beforeEach( () => state = {rawSentence: 'Hello world!'} ); 10 | 11 | it('Up & Running', () => { 12 | expect(language).to.be.ok; 13 | }); 14 | 15 | it('Compose property {language}', async () => { 16 | await language(state) 17 | expect(state).to.have.all.keys('rawSentence', 'sentence', 'language'); 18 | }); 19 | 20 | it('Detect English language', async () => { 21 | await language(state) 22 | expect(state.language).to.equal('en'); 23 | }); 24 | 25 | it('Detect non-english language', async () => { 26 | state.rawSentence = 'Hola me llamo Javi'; 27 | await language(state) 28 | expect(state.language).to.equal('es'); 29 | }); 30 | 31 | it('Can not detect language', async () => { 32 | state.rawSentence = "abcdefghijklmnopqrstvxyz"; // 🤖Language 33 | await language(state) 34 | expect(state.language).not.to.be.ok; 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/processor/relations.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | import relations from '../../src/processor/relations'; 5 | 6 | // -- Mock 7 | 8 | describe('Processor: relations', () => { 9 | 10 | let state; 11 | 12 | beforeEach( () => state = {sentence: ''}) 13 | 14 | it('Up & Running', () => { 15 | expect(relations).to.be.ok; 16 | }); 17 | 18 | it('Compose property {relations}', () => { 19 | relations(state) 20 | expect(state).to.have.all.keys('sentence', 'relations'); 21 | }); 22 | 23 | it('Detect basic relations', () => { 24 | state.sentence = 'hello world'; 25 | const value = relations(state).relations; 26 | 27 | expect(Object.keys(value).length).equal(1); 28 | expect(value).to.have.all.keys('object'); 29 | 30 | expect(value.object).to.have.all.keys('text', 'tag'); 31 | expect(value.object.text).equal('world'); 32 | expect(value.object.tag).equal('noun'); 33 | }); 34 | 35 | it('Detect complex sentences', () => { 36 | state.sentence = "Ava, Do you know if tomorrow will rain in Bangkok?" 37 | const result = relations(state).relations; 38 | 39 | expect(result).to.have.all.keys('subject', 'action', 'when', 'object', 'location'); 40 | 41 | expect(result.subject).to.have.all.keys('text', 'tag'); 42 | expect(result.subject.text).equal('ava'); 43 | expect(result.subject.tag).equal('person'); 44 | 45 | expect(result.action).to.have.all.keys('text', 'tag', 'verb'); 46 | expect(result.action.text).equal('will'); 47 | expect(result.action.tag).equal('verb'); 48 | 49 | expect(result.when).to.have.all.keys('text', 'tag'); 50 | expect(result.when.tag).equal('date'); 51 | 52 | expect(result.object).to.have.all.keys('text', 'tag'); 53 | expect(result.object.text).equal('rain'); 54 | expect(result.object.tag).equal('noun'); 55 | 56 | expect(result.location).to.have.all.keys('text', 'tag'); 57 | expect(result.location.text).equal('bangkok'); 58 | expect(result.location.tag).equal('city'); 59 | }); 60 | 61 | it('Detect if {action} is a past tense', () => { 62 | state.sentence = "I was there" 63 | const result = relations(state).relations; 64 | 65 | expect(result.action).to.have.all.keys('text', 'tag', 'verb'); 66 | expect(result.action.verb.tense).equal('past'); 67 | }); 68 | 69 | it('Detect if {action} is a future tense', () => { 70 | state.sentence = "I will be there" 71 | const result = relations(state).relations; 72 | 73 | expect(result.action).to.have.all.keys('text', 'tag', 'verb'); 74 | expect(result.action.verb.tense).equal('future'); 75 | }); 76 | 77 | it('Detect if {action} is negative', () => { 78 | state.sentence = "I won't be there" 79 | const result = relations(state).relations; 80 | 81 | expect(result.action).to.have.all.keys('text', 'tag', 'verb'); 82 | expect(result.action.verb.negative).equal(true); 83 | }); 84 | 85 | it('Detect {when} and parse to JavaScript type', () => { 86 | state.sentence = "I will be there tomorrow at 2pm" 87 | const result = relations(state).relations; 88 | 89 | expect(result.when.text instanceof Date).equal(true) 90 | }); 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /test/processor/sentiment.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | 5 | import sentiment from '../../src/processor/sentiment'; 6 | 7 | describe('Processor: sentiment', () => { 8 | 9 | let state; 10 | 11 | beforeEach( () => state = {sentence: ''}) 12 | 13 | it('Up & Running', () => { 14 | expect(sentiment).to.be.ok; 15 | }); 16 | 17 | it('Compose property {sentiment}', () => { 18 | sentiment(state) 19 | expect(state).to.have.all.keys('sentence', 'sentiment'); 20 | }); 21 | 22 | it('Detected a neutral sentiment', () => { 23 | state.sentence = 'hello world'; 24 | sentiment(state); 25 | expect(state.sentiment).equal(0); 26 | }); 27 | 28 | it('Detected a positive sentiment', () => { 29 | state.sentence = 'I love you!'; 30 | sentiment(state); 31 | expect(state.sentiment).equal(3); 32 | }); 33 | 34 | it('Detected a negative sentiment', () => { 35 | state.sentence = 'I hate you!'; 36 | sentiment(state); 37 | expect(state.sentiment).equal(-3); 38 | }); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /test/processor/taxonomy.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { assert, expect, should, eventually } from 'chai'; 4 | import taxonomy from '../../src/processor/taxonomy'; 5 | 6 | describe('Processor: Taxonomy', () => { 7 | 8 | it('Up & Running', () => { 9 | expect(taxonomy).to.be.ok; 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/processor/tokens.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | 5 | import tokens from '../../src/processor/tokens'; 6 | 7 | describe('Processor: tokens', () => { 8 | 9 | let state; 10 | 11 | beforeEach( () => state = {sentence: ''}) 12 | 13 | it('Up & Running', () => { 14 | expect(tokens).to.be.ok; 15 | }); 16 | 17 | it('Compose property {tokens}', () => { 18 | tokens(state) 19 | expect(state).to.have.all.keys('sentence', 'tokens'); 20 | }); 21 | 22 | it('Tokenize sentence', () => { 23 | state.sentence = 'hello world'; 24 | tokens(state); 25 | expect(state.tokens.length).to.equal(2); 26 | expect(state.tokens[0]).to.equal('hello'); 27 | expect(state.tokens[1]).to.equal('world'); 28 | }); 29 | 30 | it('Tokenize sentence and root verbs', () => { 31 | state.sentence = "Going to London"; 32 | tokens(state); 33 | expect(state.tokens.length).to.equal(3); 34 | expect(state.tokens[0]).to.equal('go'); 35 | expect(state.tokens[1]).to.equal('to'); 36 | expect(state.tokens[2]).to.equal('london'); 37 | }); 38 | 39 | it('Tokenize sentence and root parsing numbers', () => { 40 | state.sentence = "three apples"; 41 | tokens(state); 42 | expect(state.tokens.length).to.equal(2); 43 | expect(state.tokens[0]).to.equal('3'); 44 | }); 45 | 46 | it('Tokenize sentence and root parsing singular nouns', () => { 47 | state.sentence = "three apples"; 48 | tokens(state); 49 | expect(state.tokens.length).to.equal(2); 50 | expect(state.tokens[1]).to.equal('apple'); 51 | }); 52 | 53 | it('Tokenize sentence and use infinitive verbs', () => { 54 | state.sentence = "It's raining now in London"; 55 | tokens(state); 56 | expect(state.tokens.length).to.equal(6); 57 | expect(state.tokens[1]).to.equal('is'); 58 | expect(state.tokens[2]).to.equal('rain'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/processor/translator.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { expect } from 'chai'; 4 | 5 | import translator from '../../src/processor/translator'; 6 | 7 | describe('Processor: translator', () => { 8 | 9 | let state; 10 | beforeEach( () => state = {}) 11 | 12 | it('Up & Running', () => { 13 | expect(translator).to.be.ok; 14 | }); 15 | 16 | it('Detected an english sentence', async () => { 17 | state.rawSentence = 'hello world'; 18 | state.language = 'en'; 19 | await translator(state); 20 | 21 | expect(state.language).equal('en'); 22 | expect(state.sentence).equal(undefined); 23 | }); 24 | 25 | it('Detected a non-english sentence, translate it and identify the language', async () => { 26 | state.rawSentence = 'Hola Mundo!'; 27 | await translator(state); 28 | 29 | expect(state.language).equal('es'); 30 | expect(state.sentence).equal('Hello World!'); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /test/terminal/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import colors from 'colors'; 4 | import fetch from 'node-fetch'; 5 | // -- Core 6 | import Ava from '../../lib'; 7 | import { weather, movie, translate, conversor, any } from '../../lib/intents'; 8 | import { forecastYahoo, forecastMSN, movieDB, translator, currency, wikipedia, math } from '../../lib/actions'; 9 | // -- Internal 10 | const timeout = 10000; 11 | 12 | // -- New instance of Ava (with custom config); 13 | let ava = new Ava({ 14 | debug: true, 15 | }) 16 | 17 | // -- Prepare intents 18 | ava 19 | .intent(weather, [forecastYahoo, forecastMSN]) 20 | .intent(movie, movieDB) 21 | .intent(translate, translator) 22 | .intent(conversor, currency) 23 | .intent(any, math, wikipedia) 24 | 25 | const answer = (sentence) => { 26 | process.stdout.write('\x1Bc'); 27 | ava 28 | .listen(sentence, timeout) 29 | .then(state => { 30 | console.log(''.bold.green, state); 31 | }) 32 | .catch(error => { 33 | console.log(''.bold.red, error || `Sorry but I didn't understand you`) 34 | }) 35 | } 36 | 37 | // -- Console 38 | process.stdin.resume(); 39 | process.stdin.setEncoding('utf8'); 40 | process.stdin.on('data', (text) => { 41 | if (text === 'bye\n') { 42 | process.exit(); 43 | } else { 44 | answer(text.replace('\n', '')); 45 | } 46 | }); 47 | 48 | answer(process.argv.slice(2).join(' ')) 49 | -------------------------------------------------------------------------------- /todos.md: -------------------------------------------------------------------------------- 1 | ## 0. NOTES 2 | - Intents should dispatch a `reject()` but is better don't do it, because maybe we have more intents to execute. 3 | 4 | ## 1. KITCHENSINK 5 | - 🎯0.2.0 6 | - ESLINT 7 | - intent 🔢maths 8 | - **improve** 🔢maths action 9 | - **improve** action.wtf_wikipedia 10 | - 🎯0.1.2 11 | - translate the action.title to origin language (if it's not english) 12 | - Improve relations.spec 13 | - create a super-method for execute always all the intersections 14 | 15 | ## 2. INTENTS 16 | - 🌏Places 17 | - 🗺Directions (openstreetmaps / googlemaps) 18 | - ✈️ Flights (skyscanner) 19 | - 🚓transit (google) 20 | - 🏅sport results 21 | - 💬chat (bots) 22 | - 🖼pictures 23 | ### 2.1 Dependencies of third-parties 24 | - 🗓calendar (google) 25 | - ⏰alarm (os/google) 26 | - 👥contacts (os) 27 | 28 | ## 3. ACTIONS 29 | - 🔴entity (string person|object|location) 30 | - title (string) 31 | - text (string) 32 | - url (string) 33 | - value (object) 34 | - image (string) 35 | - [related] (array) 36 | - map/Video 37 | - text 38 | - date (date) 39 | 40 | ## 4. CLASSIFIERS 41 | // -- More info: https://github.com/nemo/natural-synaptic 42 | // -- More info: https://github.com/NaturalNode/natural 43 | 44 | ## 5. Voices 45 | - Alex (man - en) 46 | - karen/moira/samantha (woman - en) 47 | - Monica (woman - es) 48 | - Paulina (woman - es-lat) 49 | - Amelie (woman - fr) 50 | - Anna (woman - de) 51 | - Joana (woman - pt) 52 | --------------------------------------------------------------------------------