├── .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 | [](https://www.npmjs.com/package/ava-ia) [](https://travis-ci.org/ava-ia/core) [](https://www.npmjs.org/package/ava-ia) [](https://david-dm.org/ava-ia/core#info=dependencies) [](https://paypal.me/soyjavi)
4 | [](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 |
--------------------------------------------------------------------------------