forked from salesforce-misc/violet-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscript.js
252 lines (224 loc) · 8.88 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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/* 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 utils = require('violet/lib/utils');
var violet = require('violet').script();
var violetTime = require('violet/lib/violetTime')(violet);
var itemList = require('violet/lib/violetList')(violet, 'Items', 'item', 'items', 'text');
var categoryList = require('violet/lib/violetList')(violet, 'Categories', 'category', 'categories', 'text');
var quipSvc = require('./svc.js');
var Promise = require('bluebird');
module.exports = violet;
violet.addPhraseEquivalents([
["my to do", "my list", "my to do list"],
]);
violet.addInputTypes({
"itemNo": "NUMBER",
"categoryNo": "NUMBER",
'itemName': {
type: 'AMAZON.LITERAL',
// Amazon recommends/asks "to provide several hundred samples or more to address all the variations in slot value words as noted above"
sampleValues: utils.loadArrayFromFile(__dirname, 'sampleTasks.txt')
}
});
// // want to support this script in many forms
// u: Violet, add an item to the Acme Company EBC document
// v: Found the Acme Company EBC document. Which section would you like to update - Financials, EBC Agenda or ToDo?
// u: To Do
// v: Got it. What would you like to add to the checklist in the section ToDo?
// u: Make dinner reservations
// v: Got it. I added the item “make dinner reservations” to the checklist. Anything else?
// u: No thank you
/*
* Assumptions:
* a) One hardcoded document
* b) One hardcoded list
*/
var makePretty=(str)=>{
if (!str) return 'Error in Input';
str = str.trim();
return str.charAt(0).toUpperCase() + str.slice(1); // first letter uppercase
};
var ack = (response) => { response.say(['Got it.', 'Great.', 'Awesome']); }
var err = (response) => { response.say(['Sorry', 'Whoops']); }
var retry = (response) => { response.say(['Please try again', 'Would you mind repeating']); }
var apologize = (response, msg) => {
err(response);
response.say(msg);
retry(response);
}
// ToDo - make the below configurable
var tgtDocId = process.env.QUIP_TGT_DOC_ID;
var tgtDoc = 'Acme Company EBC'
var tgtSec = 'To Do'
var appendToCategory = (category, itemName) => {
console.log('appendToCategory - category', category);
var lastList = category.children;
quipSvc.appendItemsWithinSection(tgtDocId, lastList[lastList.length-1].id, [makePretty(itemName)]);
}
// define the cateogry list interactions
violet.defineGoal({
goal: categoryList.interactionGoal(),
prompt: [`Would you like to use one of these categories`],
respondTo: [{
expecting: [`use category [[categoryNo]]`],
resolve: (response) => {
var category = categoryList.getItemFromResults(response, response.get('categoryNo'));
var itemName = response.get('itemName');
response.say(`Got it. I added ${itemName} to the checklist. Anything else?`);
appendToCategory(category, response.get('itemName'));
}}, {
expecting: ['go back'],
resolve: function (response) {
ack(response);
}}]
});
violet.respondTo(['add [[itemName]] to the list'],
(response) => {
var itemName = response.get('itemName');
if (!itemName)
return apologize(response, 'I could not understand what you asked to be added.');
return quipSvc
.getItemsP(tgtDocId, /*asList*/false)
.then(categorizedItems=>{
if (categorizedItems.length==0) {
response.say(`Got it. I added [[itemName]] to the document. Anything else?`);
quipSvc.appendItemsWithinSection(tgtDocId, tgtDocId, [makePretty(itemName)]);
return;
} else if (categorizedItems.length==1) {
response.say(`Got it. I added [[itemName]] to the checklist. Anything else?`);
appendToCategory(categorizedItems[0], itemName);
return;
} else { // categorizedItems.length > 1
response.say('Sure.');
console.log('categorizedItems', categorizedItems);
response.set('Categories', categorizedItems);
categoryList.respondWithItems(response, categorizedItems);
}
});
});
violet.respondTo(['whats next to be done', 'whats next on my to do'],
(response) => {
return quipSvc.getItemsP(tgtDocId, /*asList*/true).then((items)=>{
items = flagItemsAsDone(items);
var nxtItem = items.children.find(i=>{return (i.done==false);});
if (!nxtItem) {
response.say(`There are no items that need to be done on your list`);
return;
}
response.set('tgtItem', nxtItem);
response.say(`The next item is ${nxtItem.text}`);
});
});
var markItemChecked = (docId, itemId, itemHtml) => {
// waiting on Quip team to implement this correctly
return quipSvc.modifyListItem(docId, itemId, [`<del>${itemHtml}</del>`]);
};
var flagItemsAsDone = (items) => {
// waiting on Quip team to implement this correctly
items.children = items.children.map(i=>{
if (i.html.startsWith('<del>')) i.done = true;
return i;
});
return items;
};
// define the item list interactions
violet.defineGoal({
goal: itemList.interactionGoal(),
prompt: [`Would you like to mark an item as done`],
respondTo: [{
expecting: [`mark item [[itemNo]] as {done|checked}`],
resolve: (response) => {
var item = itemList.getItemFromResults(response, response.get('itemNo'));
response.say(`Marking ${item.text} as done`);
return markItemChecked(tgtDocId, item.id, item.html);
}}, {
expecting: [`delete item [[itemNo]]`],
resolve: (response) => {
var item = itemList.getItemFromResults(response, response.get('itemNo'));
response.say(`Deleting ${item.text}`);
return quipSvc.deleteListItem(tgtDocId, item.id);
}}, {
expecting: ['go back'],
resolve: function (response) {
ack(response);
}}]
});
violet.respondTo(['what all needs to be done', 'what all is open on my to do'],
(response) => {
return quipSvc.getItemsP(tgtDocId, /*asList*/true).then((items)=>{
items = flagItemsAsDone(items);
items = items.children.filter(i=>{return (i.done==false);});
response.set('Items', items);
itemList.respondWithItems(response, items);
});
});
violet.respondTo(['whats all is on my to do'],
(response) => {
return quipSvc.getItemsP(tgtDocId, /*asList*/true).then((items)=>{
items = flagItemsAsDone(items);
items = items.children;
response.set('Items', items);
itemList.respondWithItems(response, items);
});
});
violet.respondTo(['mark item as checked'],
(response) => {
var tgtItem = response.get('tgtItem');
if (tgtItem.id && tgtItem.html) {
response.say(`Marking ${tgtItem.text} as done`);
return markItemChecked(tgtDocId, tgtItem.id, tgtItem.html);
} else
response.say(`Which item are you referring to`);
});
// use soundex https://en.wikipedia.org/wiki/Soundex
var soundex = require('soundex');
var voiceMatchScores = (voiceInp, items) => {
var _getSig = (str) => {
return str.split(' ').map(w=>{return soundex(w);}).sort();
}
var voiceInpSig = _getSig(voiceInp);
// console.log('voiceInpSig: ', voiceInpSig);
return items.map(item=>{
var itemSig = _getSig(item.text);
// console.log('item: ', item.text);
// console.log('itemSig: ', itemSig);
var matches = 0;
voiceInpSig.forEach(inpWSig=>{
var fMatched = itemSig.find(itemWSig=>{return inpWSig==itemWSig;});
if (fMatched) matches++;
});
// console.log('matchScore: ', Math.trunc(100*matches/voiceInpSig.length), matches, voiceInpSig.length);
return Math.trunc(100*matches/voiceInpSig.length);
});
};
violet.respondTo(['mark [[itemName]] as {checked|done}'],
(response) => {
return quipSvc.getItemsP(tgtDocId, /*asList*/true).then((items)=>{
var matchScores = voiceMatchScores(response.get('itemName'), items.children);
// console.log('matchScores: ', matchScores);
var hi=[], lo=[]; // indices of target items
matchScores.forEach((score, ndx)=>{
if (score>65) hi.push(ndx);
if (score<35) lo.push(ndx);
});
// console.log(hi, lo, matchScores.length);
// if high match with 1 item and low match with *all* other items
if (hi.length==1 && matchScores.length-1==lo.length) {
var tgtItem = items.children[hi[0]];
response.say(`Got it. Marking ${tgtItem.text} as done.`);
return markItemChecked(tgtDocId, tgtItem.id, tgtItem.html);
}
// if high/mid match with 2-3 items and low match with *all* other items (length-3)
if (lo.length>=matchScores.length-3) {
var matchItems = matchScores
.map((score, ndx)=>{return (score>=35) ? items.children[ndx] : null;})
.filter(i=>{return i!=null;});
response.set('Items', matchItems);
itemList.respondWithItems(response, matchItems);
return;
}
// not sure we can do better
response.say('Sorry. I could not find a match for [[itemName]]. Please try again or use a web interface.')
});
});