forked from salesforce-misc/violet-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscript.js
224 lines (198 loc) · 7.08 KB
/
script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* Copyright (c) 2017-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
'use strict';
var Promise = require('bluebird');
var utils = require('violet/lib/utils');
var violet = require('violet').script();
var violetClientTx = require('violet/lib/violetClientTx')(violet);
var violetTime = require('violet/lib/violetTime')(violet);
var yelpSvc = require('./yelp.js');
module.exports = violet;
var defaultCatsForCaching = ['korean', 'italian', 'french'];
defaultCatsForCaching = []; // disable caching during development
// sometimes multiple spoken items map to the same category and category needs to be a single word
var catAliases = require('./spokenToCategoryAliases.json');
violet.addInputTypes({
category: {
type: 'categoryType',
values: utils.loadArrayFromFile(__dirname, 'potentialCategories.txt')
}
});
// San Fran
var lat = 37.786714;
var lon = -122.411129;
var cache = {
search: {},
topCats: {},
};
var _buildCacheFromSearchResults = (categories)=>{
return yelpSvc.search(null, categories).then((results)=>{
// console.log('search results: ', JSON.stringify(results, null, 2));
cache.search[categories] = results;
});
}
var _updateCacheAggregates = (categories)=>{
var catNdx = {};
cache.search[categories].forEach(biz=>{
biz.categories.forEach(c=>{
if (!catNdx[c.alias]) catNdx[c.alias]={alias:c.alias, name:c.title, cnt:0};
catNdx[c.alias].cnt++;
});
});
// console.log('catNdx: ', catNdx);
var cats = Object.keys(catNdx);
cache.topCats[categories] = cats
.map(k=>{return catNdx[k];})
.sort((c1, c2) => {
return c2.cnt - c1.cnt;
})
.slice(0, Math.min(cats.length, 10));
// console.log('cache.topCats[categories]: ', cache.topCats[categories]);
}
var _searchAndAggregateFn = (cat) => {
return ()=>{
return _buildCacheFromSearchResults(cat)
.then(()=>{
_updateCacheAggregates(cat);
});
}
};
var buildCache = () => {
var p = yelpSvc.init(lat, lon)
.then(_searchAndAggregateFn('restaurants'));
defaultCatsForCaching.forEach(c=>{
p = p.then(_searchAndAggregateFn(c));
});
p.catch(e=>{
console.log(e);
});
};
var queryCat = (category) => {
var p = Promise.resolve();
if (!cache.topCats[category]) { // only checking 'topCats' since it is derived from 'search'
p = p.then(_searchAndAggregateFn(category));
}
return p.then(()=>{
return Promise.resolve({results: cache.search[category], facets: cache.topCats[category]});
});
}
// based on http://www.geodatasource.com/developers/javascript
// unit: default = miles
var distance = (lat1, lon1, lat2, lon2, unit) => {
console.log({lat1, lon1, lat2, lon2, unit});
var radlat1 = Math.PI * lat1/180
var radlat2 = Math.PI * lat2/180
var theta = lon1-lon2
var radtheta = Math.PI * theta/180
var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
dist = Math.acos(dist)
dist = dist * 180/Math.PI
dist = dist * 60 * 1.1515
if (unit=="K") { dist = dist * 1.609344 } // kilometers
if (unit=="N") { dist = dist * 0.8684 } // nautical miles
if (unit=="T") { dist = dist * 20 } // minutes
return dist
}
var distInTime = (tgtCoord) => {
console.log(tgtCoord);
var dist = distance(lat, lon, tgtCoord.latitude, tgtCoord.longitude, 'T');
console.log('dist: ', dist);
if (dist<5) dist = 5;
return `${Math.round(dist)} minutes`;
};
var sayTop = (response, category) => {
if (catAliases[category]) category = catAliases[category];
// console.log('sayTop request: ' + category);
return queryCat(category).then(({results, facets})=>{
// console.log('sayTop rcvd');
if (results) {
console.log('Top Item:', results[0]);
response.say(['My favorite restaurant is', 'I am a fan of', 'We are fans here of']);
response.say(results[0].name, /*quick*/true);
response.say(`It is a ${distInTime(results[0].coordinates)} walk at ${results[0].location.address1},`)
var catStr = utils.getArrAsStr(results[0].categories.map(p=>{return p.title}));
response.say(`and has ${catStr} there`, /*quick*/true)
} else {
response.say('Sorry. I could not find any ${category} restaurants');
}
});
}
var saySummary = (response, category) => {
// console.log('saySummary request: ' + category);
return queryCat(category).then(({results, facets})=>{
// console.log('saySummary rcvd: ', facets);
if (facets) {
facets = facets.filter(c=>{return c.alias !== category;});
response.say(`The best are ${facets[0].name}, ${facets[1].name}, and ${facets[2].name}`, /*quick*/true);
} else {
response.say('Sorry. I could not find any ${category} restaurants');
}
});
}
violet.respondTo(['display cache'],
(response) => {
console.log(JSON.stringify(cache, null, 2));
response.say('done');
});
violet.respondTo(['clear cache'],
(response) => {
var keyNum = o => {return Object.keys(o).length;}
response.say(`Cache used to have ${keyNum(cache.search)} search queries and ${keyNum(cache.topCats)} category metadata`);
Object.keys(cache.search).forEach(t=>{
if (t === 'restaurants') return;
delete cache.search[t];
delete cache.topCats[t];
});
response.say(`Cache updated to have ${keyNum(cache.search)} search queries and ${keyNum(cache.topCats)} category metadata`);
// response.say(`cache.topCats[category] Cleared cache.`);
});
////////////////////
// the actual script
////////////////////
violet.addPhraseEquivalents([
['favorite', 'top', 'most', 'best'],
]);
violet.respondTo([
'what is your favorite {recommended|} restaurant',
'what restaurant is your favorite {recommended|}'
],
(response) => {
return sayTop(response, 'restaurants');
});
violet.respondTo([
'what is your favorite {recommended|} [[category]] restaurant',
'what [[category]] restaurant is your favorite {recommended|}',
'what [[category]] restaurant would you recommend'
],
(response) => {
var category = response.get('category')
return sayTop(response, category);
});
violet.respondTo([
'what {types of|} restaurants would you recommend',
'I need a few recommendations for restaurants'
],
(response) => {
response.say([
'We have a number of restaurant types that I like here.',
'There are a number of great types of restaurants here.',
'There are a number of popular restaurant types here.',
]);
return saySummary(response, 'restaurants');
// response.addGoal('categoryOrTop');
});
violet.respondTo([
'what types of [[category]] restaurants would you recommend',
// 'I need a few recommendations for [[category]] restaurants'
],
(response) => {
var category = response.get('category')
response.say([
`We have a number of ${category} restaurant types that I like here.`,
`There are a number of great types of ${category} restaurants here.`,
`There are a number of popular ${category} restaurant types here.`,
]);
return saySummary(response, category);
// response.addGoal('categoryOrTop');
});
buildCache();