Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/move template selector to the selectors #163

Merged
merged 11 commits into from
Mar 19, 2018
32 changes: 7 additions & 25 deletions functions/src/actions/music-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ const dialog = require('../dialog');
const feeders = require('../extensions/feeders');
const {getSuggestionProviderForSlots} = require('../extensions/suggestions');
const {
extractRequrements,
getMatchedTemplates,
getMatchedTemplatesExactly,
getPromptsForSlots,
getRequiredExtensionHandlers,
} = require('../slots/slots-of-template');
Expand Down Expand Up @@ -233,31 +230,16 @@ function generateAcknowledge ({app, slotScheme, newValues}) {

debug('and get new slots:', newValues);

const acknowledgeRequirements = extractRequrements(slotScheme.acknowledges);
const template = selectors.find(slotScheme.acknowledges, {
prioritySlots: newNames,
});

// find the list of acknowledges which match recieved slots
let validAcknowledges = getMatchedTemplatesExactly(
acknowledgeRequirements,
newNames
);

if (!validAcknowledges || validAcknowledges.length === 0) {
validAcknowledges = getMatchedTemplates(
acknowledgeRequirements,
newNames
);

if (!validAcknowledges || validAcknowledges.length === 0) {
warning(`there is no valid acknowledges for ${newNames}. Maybe we should write few?`);
return Promise.resolve(null);
}

debug('we have partly matched acknowledges', validAcknowledges);
} else {
debug('we have exactly matched acknowledges', validAcknowledges);
if (!template) {
debug(`we haven't found right acknowledge maybe we should create few for "${newNames}"`);
return Promise.resolve(null);
}

const template = _.sample(validAcknowledges);
debug('we got matched acknowledge', template);

// mustachejs doesn't support promises on-fly
// so we should solve all them before and fetch needed data
Expand Down
65 changes: 65 additions & 0 deletions functions/src/configurator/parsers/extract-requirements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const mustache = require('mustache');

const extensions = require('../../slots/extensions');

/**
* For each template extract required slots
*
* - it could be plain:
* {{coverage}} - good place!' => ['coverage']
*
* - hierarchy:
* Ok! Lets go with {{creator.title}} band!` => ['creator']
*
* - with extensible resovlers:
* 'Ok! Lets go with {{__resolvers.creator.title}} band!' =>
* ['creatorId']
*
* each resolver has list of requirements for example for 'creator':
* ['creatorId']
*
* @param templates
*/
function extractRequrements (templates) {
return templates && templates
.map(template => ({
template,
requirements: getListOfRequiredSlots(template)
.reduce(
(acc, item) => {
const splitName = item.split('.');
const extType = extensions.getExtensionTypeFromValue(splitName[0]);
const extension = extensions.getExtensionTypeSet(extType)(splitName[1]);
if (!extension) {
return acc.concat(item);
}
let requirements = extension.requirements;
if (typeof requirements === 'function') {
requirements = requirements(splitName.slice(2).join('.'));
}
return acc.concat(requirements);
}, []
)
// some slots could be described in temples like slotName.field
// we should consider slotName only and drop field here
.map(slot => slot.split('.')[0])
}));
}

/**
* Get list of slots which are needed for this template
*
* @param {string} template
* @returns {Array}
*/
function getListOfRequiredSlots (template) {
return mustache
.parse(template)
.filter(token => token[0] === 'name')
.map(token => token[1]);
}

module.exports = {
extractRequrements,
getListOfRequiredSlots,
};
17 changes: 17 additions & 0 deletions functions/src/configurator/resolvers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* All resolvers should be here
*
* Resolver features:
*
* - calculate on-fly new values.
* For example: title of collection by its id,
* name of artist by it's id and etc
*
*
*
* They could be used as fulfillments of Slots
*/

const builder = require('../builder');

module.exports = builder.build({root: __dirname});
4 changes: 2 additions & 2 deletions functions/src/configurator/selectors/condition-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const {debug, warning} = require('../../utils/logger')('ia:selectors:condition-s
* @returns {string}
*/
function find (options, context) {
debug('Select option by condition');
debug('select option by condition');

if (!context || typeof context !== 'object') {
throw new Error('context argument should be defined');
Expand All @@ -24,7 +24,7 @@ function find (options, context) {
try {
return math.eval(condition, context);
} catch (error) {
warning(`Get error from Math.js:`, error && error.message);
debug(`Get error from Math.js:`, error && error.message);
return false;
}
}
Expand Down
93 changes: 93 additions & 0 deletions functions/src/configurator/selectors/template-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const _ = require('lodash');

const extractor = require('../parsers/extract-requirements');
const {debug, warning} = require('../../utils/logger')('ia:selectors:template-selector');

/**
* Choose template with maximum slots coverage
*
* @param options
* @param context
* @returns {Promise.<null>}
*/
function find (options, context) {
debug('sgelect option as template');

const prioritySlots = context.prioritySlots;
debug('the priority slots are:', prioritySlots);

const acknowledgeRequirements = extractor.extractRequrements(options);

// find the list of acknowledges which match recieved slots
let validAcknowledges = getMatchedTemplatesExactly(
acknowledgeRequirements,
prioritySlots
);

if (!validAcknowledges || validAcknowledges.length === 0) {
validAcknowledges = getMatchedTemplates(
acknowledgeRequirements,
prioritySlots
);

if (!validAcknowledges || validAcknowledges.length === 0) {
warning(`there is no valid templates for ${prioritySlots}. Maybe we should write few?`);
return null;
}

debug('we have partly matched template', validAcknowledges);
} else {
debug('we have exactly matched template', validAcknowledges);
}

// TODO: maybe we should return all matched templates here
// and choose one on another middleware/selector
return _.sample(validAcknowledges);
}

/**
* Get list of templates which match slots
*
* @param {Array} templateRequirements
* @param {Object} slots
* @returns {Array
*/
function getMatchedTemplates (templateRequirements, slots) {
return templateRequirements && templateRequirements
.filter(
({requirements}) => requirements.every(r => _.includes(slots, r))
)
.map(({template}) => template);
}

/**
* Get list of templates which match slots exactly
*
* @param {Array} templates
* @param {Object} slots
* @returns {Array
*/
function getMatchedTemplatesExactly (templateRequirements, slots) {
const numOfSlots = slots.length;
return templateRequirements && templateRequirements
.filter(({requirements}) => {
const intersection = _.intersection(requirements, slots);
return intersection.length === requirements.length &&
intersection.length === numOfSlots;
})
.map(({template}) => template);
}

module.exports = {
find,

/**
* we support options which has slots to fill in
* @param options
*/
support: (options) => options.some(o => extractor.getListOfRequiredSlots(o).length > 0),

// we extract those function for test purpose in the first place
getMatchedTemplates,
getMatchedTemplatesExactly,
};
36 changes: 22 additions & 14 deletions functions/src/dialog/middlewares/replace-speech-if-muted.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
const selectors = require('../../configurator/selectors');
const playback = require('../../state/playback');
const strings = require('../../strings').dialog.playSong;
const {debug} = require('../../utils/logger')('ia:dialog:middlewares:replace-speech-if-muted');

/**
* Process options before play audio
*
* @param options
* @param options.muteSpeech {Boolean} - Mute speech before play audio
* @returns {Object}
*/
function replaceSpeechIfMutedMiddleware (app, options) {
if (playback.isMuteSpeechBeforePlayback(app)) {
return Object.assign({}, options, {speech: strings.speech});
module.exports = (availableStrings) => {
/**
* Process options before play audio
* by replacing speech if we decide to mute speech before play song
*
* @param options
* @param options.muteSpeech {Boolean} - Mute speech before play audio
* @returns {Object}
*/
function replaceSpeechIfMutedMiddleware (app, options) {
debug('start');
const strings = selectors.find(availableStrings, options);
if (playback.isMuteSpeechBeforePlayback(app)) {
debug('apply speech');
return Object.assign({}, options, {speech: strings.speech});
}
debug('skip');
return options;
}
return options;
}

module.exports = () => replaceSpeechIfMutedMiddleware;
return replaceSpeechIfMutedMiddleware;
};
4 changes: 3 additions & 1 deletion functions/src/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const replaceSpeechIfMuted = require('./dialog/middlewares/replace-speech-if-mut
const mathjsExtensions = require('./mathjs');

module.exports = () => {
const pipeline = new Pipeline().use(replaceSpeechIfMuted());
const pipeline = new Pipeline()
.use(replaceSpeechIfMuted(require('./strings').dialog.playSong));

dialog.use(pipeline);

mathjsExtensions.patch();
Expand Down
79 changes: 0 additions & 79 deletions functions/src/slots/slots-of-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,6 @@ const {debug, warning} = require('../utils/logger')('ia:slots');

const extensions = require('./extensions');

/**
* For each template extract required slots
* - it could be plain:
* {{coverage}} - good place!' => ['coverage']
*
* - hierarchy:
* Ok! Lets go with {{creator.title}} band!` => ['creator']
*
* - with extensible resovlers:
* 'Ok! Lets go with {{__resolvers.creator.title}} band!' =>
* ['creatorId']
*
* each resolver has list of requirements for example for 'creator':
* ['creatorId']
*
* @param templates
*/
function extractRequrements (templates) {
return templates && templates
.map(template => ({
template,
requirements: getListOfRequiredSlots(template)
.reduce(
(acc, item) => {
const splitName = item.split('.');
const extType = extensions.getExtensionTypeFromValue(splitName[0]);
const extension = extensions.getExtensionTypeSet(extType)(splitName[1]);
if (!extension) {
return acc.concat(item);
}
let requirements = extension.requirements;
if (typeof requirements === 'function') {
requirements = requirements(splitName.slice(2).join('.'));
}
return acc.concat(requirements);
}, []
)
// some slots could be described in temples like slotName.field
// we should consider slotName only and drop field here
.map(slot => slot.split('.')[0])
}));
}

/**
* Get list of slots which need for this template
*
Expand Down Expand Up @@ -87,39 +44,6 @@ function getListOfRequiredExtensions (template) {
.filter(({extType, name}) => extType && name);
}

/**
* Get list of templates which match slots
*
* @param {Array} templateRequirements
* @param {Object} slots
* @returns {Array
*/
function getMatchedTemplates (templateRequirements, slots) {
return templateRequirements && templateRequirements
.filter(
({requirements}) => requirements.every(r => _.includes(slots, r))
)
.map(({template}) => template);
}

/**
* Get list of templates which match slots exactly
*
* @param {Array} templates
* @param {Object} slots
* @returns {Array
*/
function getMatchedTemplatesExactly (templateRequirements, slots) {
const numOfSlots = slots.length;
return templateRequirements && templateRequirements
.filter(({requirements}) => {
const intersection = _.intersection(requirements, slots);
return intersection.length === requirements.length &&
intersection.length === numOfSlots;
})
.map(({template}) => template);
}

/**
* Get prompts which match covers 1st slot
* and has the maximum intersection with other slots
Expand Down Expand Up @@ -171,11 +95,8 @@ function getRequiredExtensionHandlers (template) {
}

module.exports = {
extractRequrements,
getListOfRequiredExtensions,
getListOfRequiredSlots,
getMatchedTemplates,
getMatchedTemplatesExactly,
getPromptsForSlots,
getRequiredExtensionHandlers,
};
Loading