UNPKG

tpmkms

Version:

Reusable libraries for Entodicton, a natural language to json converter

1,003 lines (933 loc) 34.6 kB
const { knowledgeModule, ensureTestFile, where } = require('./runtime').theprogrammablemind const { defaultContextCheck, propertyToArray } = require('./helpers') const edible = require('./edible') const events = require('./events') const sizeable = require('./sizeable') const countable = require('./countable') const { isMany, getCount } = require('./helpers') ensureTestFile(module, 'fastfood', 'test') ensureTestFile(module, 'fastfood', 'instance') const fastfood_tests = require('./fastfood.test.json') const fastfood_instance = require('./fastfood.instance.json') /* hello -> to order can I help you? -> expect order a big mac and large fries with a large coke gimme a big mac and large fries with a large coke make them large no small instead give me a combo one / give me a single combo / i want combo one i want 2 hamburgers -> what kind? big mac big mac quarter pounder single and double combo single combo combo one and two double hamburger number 1 and 2 number 1 2 and 3 combo 1 through 5 */ const template = { configs: [ "food is countable", "drinks are countable", "bacon modifies deluxe", { priorities: [ { "context": [['bacon_deluxe', 0], ['list', 0]], "choose": [0] } ] }, "chicken modifies sandwich", { priorities: [ { "context": [['chicken_sandwich', 0], ['list', 0]], "choose": [0] } ] }, "premium modifies cod", { priorities: [ { "context": [['premium_cod', 0], ['list', 0]], "choose": [0] } ] }, "ultimate chicken modifies grill", { priorities: [ { "context": [['ultimate_chicken_grill', 0], ['list', 0]], "choose": [0] } ] }, // "10 piece modifies nuggets", "chicken modifies club", "asiago ranch modifies chicken club", "crispy modifies chicken", "crispy chicken modifies club", "club is a sandwich", { priorities: [ { "context": [['asiago_ranch_chicken_club', 0], ['list', 0]], "choose": [0] } ] }, "waffle modifies fries", { priorities: [ { "context": [['waffle_fry', 0], ['list', 0]], "choose": [0] } ] }, "loaded modifies fries", "chili modifies fries", "garden modifies salad", "caesar modifies salad", "cheese modifies potato", "waffle fries are french fries", "mango modifies passion", "wild modifies berry", "strawberry modifies banana", "strawberry, guava, mango passion, wild berry and strawberry banana modify smoothie", "strawberry, guava, mango passion, wild berry, and strawberry banana are countable", "smoothie modifies ingredients", "strawberry, guava, mango passion, wild berry, and strawberry banana are smoothie ingredients", "a smoothie is a drink", "french fries and waffle fries are fries", "single, double, triple, baconator, and bacon deluxe are hamburgers", // "spicy homestyle asiago ranch chicken club 10 piece nuggets ultimate chicken grill and premium cod are sandwiches", "spicy, homestyle, asiago ranch chicken club, ultimate chicken grill and premium cod are sandwiches", "meals are food", "a combo is a meal", "chili is a meal", "a shake is a drink", "vanilla modifies shake", "mango passion modifies shake", "strawberry modifies shake", "guava modifies shake", "chocolate modifies shake", "banana modifies shake", "wild berry modifies shake", "frosty is a drink", "vanilla modifies frosty", "chocolate modifies frosty", "breakfast modifies baconator", "french toast modifies sandwich", "egg modifies muffin", "chicken on modifies french toast", "pancake modifies platter", "double sausage modifies muffin", "apple pecan modifies salad", "spicy modifies caesar salad", "taco modifies salad", "southwest avacado modifies salad", "breakfast modifies meals", "bacon modifies cheeseburger", "junior modifies bacon cheeseburger", "go modifies wrap", "a go wrap is a sandwich", "chicken modifies go wrap", "breakfast baconator, french toast sandwich, egg muffin, chicken on french toast, pancake platter, double sausage muffin, pancakes, french toast and oatmeal are breakfast meals", "single, double, triple, baconator, bacon deluxe, spicy homestyle and premium cod are meals", "coca modifies cola", "diet modifies coke", "iced modifies tea", "coke, coca cola, diet coke, sprite, fanta, barqs and iced tea are pop", "sweet black modifies coffee", "french vanilla modifies coffee", "cappuccino modifies coffee", "mocha modifies shake", "caramel modifies shake", "strawberry modifies lemonade", "wild berry modifies lemonade", "plain modifies lemonade", "fries and drinks are modifications", // { stop: true }, // TODO Future see above note { query: "(combo one) and (2 combo twos)", skipSemantics: true }, // { query: "(2 mango passion and (3 strawberry)) smoothies", skipSemantics: true }, // { query: "(2 mango passion and (3 strawberry)) smoothies", skipSemantics: true }, "broccoli and cheddar literally modifies potato", "bacon and cheddar literally modifies potato", "chili and cheese literally modifies potato", "kids modifies meal", "value modifies meal", "bottled modifies water", "bottled water is a drink", "apple modifies slice", "natural cut modifies fries", "hamburgers, cheeseburgers, crispy chicken and nuggets are kids meals", { where: where(), operators: [ "((meal/* && context.comboNumber == undefined) [comboMeal] (combo/*))", "((combo/*) [comboNumber] (number/* || numberNumberCombo/*))", "((numberNumberCombo/1) [numberNumberCombo_combo|] (combo/0))", "((number/0,1 && context.instance == false) [numberNumberCombo] (number/0,1 && instance == true))", "((combo/*) [([withModification|with] ([modification]))])", ], floaters: ['instance'], priorities: [ // { "context": [['counting', 0], ['strawberry_smoothie', 0], ], "choose": [0] }, { "context": [['cheese_potato', 0], ['broccoli_list_cheddar_potato', 0]], "choose": [1] }, { "context": [['chicken_go_wrap', 0], ['chicken_sandwich', 0], ], "choose": [0] }, { "context": [['strawberry_banana', 0], ['smoothie', 0], ], "choose": [0] }, { "context": [['number', 0], ['numberNumberCombo', 0], ], "choose": [0] }, { "context": [['list', 0], ['numberNumberCombo', 0], ], "choose": [0] }, // TODO take this out make the server side learn if from "(combo one) and (two combo twos)" . (prioritized1) 'and' (prioritized2) where 1+2 came from and build the cp that way { "context": [['combo',0], ['number', 0], ['list', 0], ['number', 0],['combo', 0],['number',0]], ordered: true, choose: [0,1,3,4,5] }, { "context": [['number',0], ['smoothie_ingredient', 0], ['list', 0], ['number', 0],['smoothie_ingredient', 0],['smoothie',0]], ordered: true, choose: [0,1,3,4] }, // { "context": [['single',0], ['list', 0], ['double', 0],['combo', 0]], ordered: true, choose: [0,1,2] }, // { "context": [['list', 0], ['comboMeal', 0]], choose: [0] }, { "context": [['list', 0], ['smoothie', 0]], choose: [0] }, { "context": [['list', 0], ['smoothie', 1]], choose: [0] }, { "context": [['smoothie_ingredient', 1], ['list', 0], ['number', 1], ['smoothie_ingredient', 1], ['smoothie', 1]], ordered: true, choose: [2,3] }, { "context": [['smoothie_ingredient', 1], ['list', 0], ['smoothie_ingredient', 1], ['smoothie', 1]], ordered: true, choose: [1] }, { "context": [['list', 0], ['number', 1], ['combo', 1], ['number', 1]], ordered: true, choose: [2,3] }, { context: [['comboNumber', 0], ['counting',0]], choose: [0] }, // { context: [['counting', 0], ['smoothie',0]], choose: [0] }, { context: [['list', 0], ['food',0], ['combo', 0]], ordered: true, choose: [0,1] }, { context: [['combo', 0], ['number', 0], ['list',0], ['number', 0]], ordered: true, choose: [1,2,3] }, { context: [['combo', 0], ['comboNumber', 0], ['list', 1]], ordered: true, choose: [1] }, { context: [['number', 0], ['numberNumberCombo', 0], ['list', 1]], ordered: true, choose: [1] }, { context: [['number', 1], ['numberNumberCombo', 1], ['combo', 0]], ordered: true, choose: [2] }, { context: [['combo', 1], ['list', 0], ['number', 1], ['combo', 1]], ordered: true, choose: [2,3] }, { context: [['list', 0], ['number', 1], ['combo', 1]], ordered: true, choose: [1,2] }, { context: [['list', 0], ['number', 1], ['smoothie_ingredient', 1], ['smoothie', 0]], ordered: true, choose: [1,2] }, ], generators: [ { where: where(), match: ({context}) => false && context.marker == 'combo' && context.comboNumber, apply: async ({context, g}) => await g(context.comboNumber), } ], bridges: [ { id: 'modification' }, { id: 'withModification', level: 0, before: ['preposition'], generatorp: async ({context, gp}) => `with ${await gp(context.modifications)}`, bridge: "{ ...next(operator), modifications: after[0], flatten: false }", }, { id: 'withModification', level: 1, isA: ['preposition'], bridge: "{ ...next(before[0]), postModifiers: append(before[0].postModifiers, ['modifications']), modifications: operator }", }, { id: 'numberNumberCombo_combo', convolution: true, isA: ['food'], before: ['combo', 'counting'], bridge: "{ ...next(operator), modifiers: append(before[0].modifiers, ['comboNumber']), comboNumber: before[0], word: 'combo', flatten: true }", }, { id: 'comboMeal', convolution: true, before: ['meal', 'combo', 'counting'], bridge: "{ ...next(after[0]), modifiers: append(before[0].modifiers, ['type']), type: before[0], flatten: true }", }, { id: 'comboNumber', convolution: true, before: ['combo', 'preposition'], bridge: "{ ...next(before[0]), postModifiers: append(before[0].postModifiers, ['comboNumber']), comboNumber: after[0], instance: true, flatten: true }", }, { id: 'numberNumberCombo', convolution: true, isA: ['food'], before: ['combo', 'comboNumber'], bridge: "{ ...next(operator), word: 'number', combo: true, postModifiers: append(before[0].postModifiers, ['comboNumber']), comboNumber: after[0], flatten: true }", }, ] }, // TODO "sour cream and chives" is a phrase or maybe just "sour cream and chives" // "sour cream and literally modifies chives", { priorities: [ { context: [['bacon_list_cheddar_potato', 0], ['cheese_potato', 0]], choose: [0] }, // TODO make this automatic { context: [['crispy_chicken', 0], ['chicken_sandwich', 0], ['chicken_club', 0]], choose: [0] }, // TODO maybe prefer the one that takes more arguments? { context: [['chili_list_cheese_potato', 0], ['cheese_potato', 0]], choose: [0] }, { context: [['list', 0], ['junior', 0], ['crispy', 0], ['chicken', 0], ['club', 0]], ordered: true, choose: [1,2,3,4] }, { context: [['meal', 0], ['list', 0], ['meal', 0], ['comboMeal', 0]], ordered: true, choose: [0,1,2] }, // { context: [['crispy_chicken_club', 0], ['chicken_club', 0], ['chicken_sandwich', 0]], choose: [0] }, { context: [['breakfast_baconator', 0], ['breakfast_meal', 0]], choose: [0] }, { context: [['list', 0], ['article',0], ['smoothie_ingredient', 0], ['smoothie', 0]], ordered: true, choose: [1,2] }, ] }, "junior modifies crispy chicken club", "nuggets, junior bacon cheeseburgers, chicken go wraps and junior crispy chicken clubs are value meals", "combos, chili, fries and drinks are sizeable", { semantics: [ { // split "sprite and fanta" into separate things so the ask will pick them up match: ({context}) => context.marker == 'list' && context.topLevel && !context.flatten, apply: async ({context, s}) => await s({...context, flatten: true}), }, { where: where(), match: ({context, api}) => context.marker == 'controlEnd' && api.hasAskedForButNotAvailable(), apply: async ({context, api, gp, toContext, verbatim}) => { const naArray = api.getAskedForButNotAvailable() naArray.forEach((f) => f.paraphrase = true) const naContext = toContext(naArray) verbatim(`The following are not menu items: ${await gp(naContext)}`) // allow other motivation to run context.cascade = true } }, { where: where(), match: ({context, api}) => context.marker == 'controlEnd' && api.hasAskedForButNotAvailableModification(), apply: async ({context, api, gp, toContext, verbatim}) => { const naArray = [] for (const args of api.getAskedForButNotAvailableModification()) { const args = { item, modification } // return `${gp(item)} can not be modified with ${gp(modification)}.` naArray.push(`XXX can not be modified with ${await gp(modification)}.`) } verbatim(naArray.join(' ')) // allow other motivation to run context.cascade = true } }, ] }, ({ask, api}) => { // see if followup for drink is needed const hasDrink = (isA, item) => { let hasDrink = false for (const modification of (item.modifications || [])) { if (isA(modification.id, 'drink')) { hasDrink = true break } } return hasDrink } const needsDrink = (item) => { return item.needsDrink } const askAbout = ({api, isA}) => { const items = [] for (const item of api.items()) { if (needsDrink(item) && !hasDrink(isA, item)) { items.push(item) } } return items } ask([ { where: where(), oneShot: false, onNevermind: ({verbatim, ...args}) => { // this is cross km boundaries from the dialogues km to this one so the api if for dialogs. // i need to get the one for fastfood here. const api = args.kms.fastfood.api const needsDrink = askAbout({ args, api }) for (const item of needsDrink) { api.remove(item) } }, matchq: (args) => askAbout(args).length > 0 && args.context.marker == 'controlEnd', applyq: (args) => { args.context.cascade = true const needsDrink = askAbout(args) // const details = args.gp({ marker: 'list', value: needsDrink.map((item) => item.food)}) if (needsDrink.length > 1) { return `What drinks do you want?` } else { return `What drink do you want?` } }, matchr: (args) => { if (args.isA(args.context.marker, 'drink') && askAbout(args).length > 0) { const needsDrink = askAbout(args) if (args.api.isAvailableModification(needsDrink[0].food, { ...args.context , id: args.context.value })) { return true } } return false }, applyr: (args) => { // TODO check for is available for all modifications const needsDrink = askAbout(args) const { api, context } = args if (isMany(context)) { let count = getCount(context) || Number.MAX_SAFE_INTEGER for (const item of needsDrink) { if (count < 1) { break } count -= 1 api.addDrink(item.item_id, { id: context.value }) } } else { const item_id = needsDrink[0].item_id api.addDrink(item_id, { id: context.value }) } } }, ]) }, { operators: [ "([change] (meal/* || drink/*) (to/1))", ], hierarchy: [ ['meal', 'toAble'], ['drink', 'toAble'], ], bridges: [ { id: "change", isA: ['verb'], localHierarchy: [ ['thisitthat', 'meal'], ['thisitthat', 'drink'] ], generatorp: async ({context, gp}) => `change ${await gp(context.from)} to ${await gp(context.to)}`, bridge: "{ ...next(operator), from: after[0], to: after[1].toObject }", semantic: async ({context, api, e}) => { const state = api.state const eFrom = (await e(context.from)).evalue const from = state.getIdCombo(eFrom.fromSTM ? eFrom : context.from) const to = state.getIdCombo(context.to) for (const item of api.items()) { if (item.id == from.id) { api.modify(item, { id: to.id, food: context.to }) } } } }, ], priorities: [ { context: [['combo', 0], ['number',1], ['list', 0], ['combo', 0]], ordered: true, choose: [0,1] }, { context: [['list', 0], ['combo',0], ['number',1]], ordered: true, choose: [1,2] }, { context: [['list', 0], ['combo',0], ['list',1]], ordered: true, choose: [1,2] }, { context: [['mango', 0], ['passion',0], ['list', 0]], ordered: true, choose: [0,1] }, { context: [['number', 1], ['mango_passion',1], ['list', 0]], ordered: true, choose: [0,1] }, { context: [['mango', 0], ['mango_passion',0], ['passion',0], ['list', 0]], ordered: true, choose: [0,1,2] }, { context: [['drink', 0], ['list',0], ['combo',0], ['number', 0]], ordered: true, choose: [2,3] }, { context: [['drink', 1], ['list',0], ['combo',0], ['number', 0]], ordered: true, choose: [2,3] }, { context: [['withModification', 0], ['modification', 1], ['list', 0], ['modification', 1]], ordered: true, choose: [2] }, { context: [['withModification', 0], ['modification', 1], ['list', 0], ['combo', 1]], ordered: true, choose: [0] }, { context: [['combo', 2], ['list', 0], ['combo', 1], ['withModification', 1]], ordered: true, choose: [3] }, ], }, { operators: [ "([remove|remove,delete,drop,ditch,forget,no] (food/*))", "([reset|reset,restart,clear])", ], bridges: [ { id: 'remove', isA: ['verb'], bridge: "{ ...next(operator), remove: after[0], postModifiers: ['remove'] }", semantic: ({context, api}) => { const state = api.state for (const item of api.items()) { if (state.match(context.remove, item)) { api.remove(item) } } } }, { id: 'reset', isA: ['verb'], bridge: "{ ...next(operator) }", semantic: ({context, api}) => { api.reset() } }, ], }, ], } class API { initialize({ objects, config }) { this._objects = objects this.reset() } show() { this._objects.show = this._objects.items } toItem(item_id) { if (Array.isArray(item_id)) { return this._objects.items[item_id[0]].modifications[item_id[1]] } else { return this._objects.items[item_id] } } new_item_id() { const item_id = this._objects.item_id_counter this._objects.item_id_counter += 1 return item_id } // returns an item id so things can be updated if needed add(item) { item.item_id = this.new_item_id() if (!item.modifications) { item.modifications = [] } item.modifications.forEach((modification, index) => { modification.item_id = [item.item_id, index] }) this._objects.items.push(item) } reset() { this._objects.items = [] this._objects.notAvailable = [] this._objects.notAvailableModification = [] this._objects.item_id_counter = 0 } get(item_id) { return this.toItem(item_id) } modify(item, changes) { Object.assign(this.toItem(item.item_id), changes) } remove(item) { this._objects.items = this._objects.items.filter( (i) => i.item_id !== item.item_id ) } items() { return [...this._objects.items] } fixUpItemId(item) { if (item.id == 'coke') { item.id = 'coca_cola' } if (item.id == 'fry') { item.id = 'french_fry' } } addDrink(item_id, drink) { const item = this.toItem(item_id) this.fixUpItemId(drink) item.modifications.push(drink) item.needsDrink = false } say(response) { this._objects.response = response } // return true if you want the NLI layer to handle this hasAskedForButNotAvailable(item) { return this._objects.notAvailable.length > 0 } hasAskedForButNotAvailableModification(item) { return this._objects.notAvailableModification.length > 0 } getAskedForButNotAvailable(item) { const na = this._objects.notAvailable this._objects.notAvailable = [] return na } getAskedForButNotAvailableModification(item) { const na = this._objects.notAvailableModification this._objects.notAvailableModification = [] return na } addAskedForButNotAvailable(item) { this._objects.notAvailable.push(item) } addAskedForButNotAvailableModification(item, modification) { this._objects.notAvailableModification.push({item, modification}) } isAvailableModification(food, modification) { if (this.isAvailable(modification)) { if (this.args.isA(modification.id, 'fry') || this.args.isA(modification.id, 'pop')) { return true } } } isAvailable(item) { item.id = item.id || item.value if (item.id == 'chicken_nugget') { if (![4,5,6,10].includes(item.pieces)) { return false } if ([4,6].includes(item.pieces)) { item.combo = true } } if (['hamburger', 'cheeseburger', 'junior_bacon_cheeseburger', 'junior_crispy_chicken_club', 'chicken_go_wrap'].includes(item.id)) { item.combo = true } if (item.combo) { item.needsDrink = true for (const modification of item.modifications || []) { // TODO check for awailable if (this.args.isA(modification.id, 'drink')) { item.needsDrink = false } } } this.fixUpItemId(item) return [ "hamburger", "cheeseburger", "crispy_chicken", "broccoli_list_cheddar_potato", "bacon_list_cheddar_potato", "chili_list_cheese_potato", "chicken_go_wrap", "junior_bacon_cheeseburger", "junior_crispy_chicken_club", "breakfast_baconator", "french_toast_sandwich", "egg_muffin", "chicken_on_french_toast", "pancake_platter", "double_sausage_muffin", "pancake", "french_toast", "oatmeal", "chicken_nugget", "double", "french_fry", "single", "triple", 'baconator', 'bacon_deluxe', 'spicy', 'homestyle', 'asiago_range_chicken_club', 'ultimate_chicken_grill', 'premium_cod', "waffle_fry", "strawberry_smoothie", "guava_smoothie", "mango_passion_smoothie", "wild_berry_smoothie", "strawberry_banana_smoothie", "coca_cola", "diet_coke", "barq", "fanta", "sprite", "iced_tea", "sweet_black_coffee", "french_vanilla_coffee", "cappuccino_coffee", "mocha_shake", "caramel_shake", "lemonade", "strawberry_lemonade", "wild_berry_lemonade", "loaded_fry", "chili_fry", "garden_salad", "caesar_salad", "chili", "cheese_potato", "vanilla_shake", "mango_passion_shake", "strawberry_shake", "guava_shake", "chocolate_shake", "banana_shake", "wild_berry_shake", "vanilla_frosty", "chocolate_frosty", "apple_pie", "apple_pecan_salad", "spicy_caesar_salad", "taco_salad", "southwest_avacado_salad", ].includes(item.id) } getCombo(number) { const map = { 1: 'single', 2: 'double', 3: 'triple', 4: 'baconator', 5: 'bacon_deluxe', 6: 'spicy', 7: 'homestyle', 8: 'asiago_range_chicken_club', 9: 'ultimate_chicken_grill', 10: 'chicken_nugget', 11: 'premium_cod', } return map[number] } canBeCombo(id) { return this.getComboNumber(id) > 0 } getComboNumber(id) { const combos = [ 'single', 'double', 'triple', 'baconator', 'bacon_deluxe', 'spicy', 'homestyle', 'asiago_range_chicken_club', 'ultimate_chicken_grill', 'chicken_nugget', 'premium_cod', ] return combos.findIndex((e) => e == id) + 1 } } const api = new API() class State { constructor(api) { this.api = api } getIdCombo(food) { let id, combo if (food.comboNumber?.marker == 'numberNumberCombo') { id = this.api.getCombo(food.comboNumber.comboNumber.value) if (!id) { this.api.addAskedForButNotAvailable(food) return { done: true } } combo = true } else if (food.comboNumber) { id = this.api.getCombo(food.comboNumber.value) if (!id) { this.api.addAskedForButNotAvailable(food) return { done: true } } combo = true } else if (food.marker == 'combo') { id = food.type?.value combo = true } else { id = food.value combo = !!food.combo } if (id == 'nugget') { id = 'chicken_nugget' } return { id, combo } } match(pattern, item) { Object.assign(pattern, this.getIdCombo(pattern)) if (pattern.id == item.id) { return true } if (!pattern.id) { if (pattern.combo == item.combo) { return true } } } add(food) { let quantity = 1 if (food.quantity) { quantity = food.quantity.value } const { id, combo, done } = this.getIdCombo(food) if (done) { return } const addSize = (item, data) => { if (item.size) { data.size = item.size.value } return data } const getAvailableChildren = (item) => { // see if this is a categories of items const descendants = this.api.args.hierarchy.descendants(item.id) const available = [] for (const descendant of descendants) { if (this.api.isAvailable({ id: descendant})) { available.push(descendant) } } return available } let modifications const addsInsteadOfModifications = [] if (food.modifications) { modifications = [] for (const modification of propertyToArray(food.modifications.modifications)) { if (modification.size) { food.size = modification.size } // if not a modification treat as top level request if (!this.api.isAvailableModification(food, { ...modification, id: modification.value })) { if (this.api.isAvailable(modification)) { //this.add(modification) addsInsteadOfModifications.push(modification) } else { this.api.addAskedForButNotAvailable(modification) } } else { addSize(modification, { id: modification.value }) modifications.push(addSize(modification, { id: modification.value })) } } } let pieces if (food.pieces) { pieces = food.pieces.count.value } else { if (id == 'chicken_nugget') { // TODO ask how many pieces pieces = 10 } } for (let i = 0; i < quantity; ++i) { const item = addSize(food, { id, combo, modifications, pieces, food }) if (!this.api.isAvailable(item)) { const available = [] for (const descendant of this.api.args.hierarchy.descendants(food.marker)) { if (this.api.isAvailable({ id: descendant })) { available.push(descendant) } } // this sentence runs but it doesnt setup the hierarchy: 'combo 1, 2, 3, 4, 5, 6, 7, 9, 10, and 11 are combos' // i made a wrong design choice when i setup the phrase 'combo 1 etc'. I should have mapped that to the 'single_combo' // but instead had it be a combo with a comboNumber property. That means the language layer doesnt know about the mapping // so that phrase doesnt work. if I set it up the other way that phrase would work. This is just a demo and I have other // demoes to write so i am not fixin that and instead will do || is a combo if (available.length > 0 || food.marker == 'combo') { this.api.args.ask([ { where: where(), oneShot: true, matchq: ({context}) => context.marker == 'controlEnd', applyq: () => { // args.context.cascade = true const word = food.word return `What kind of ${word}?` }, semanticsr: [ // stuipid hack one because i didnt put combo fully into the NLI layer { where: where(), match: ({context, callId, isA, api}) => { return api.canBeCombo(context.marker) }, apply: ({context}) => { const comboNumber = { value: api.getComboNumber(context.marker) } food.comboNumber = comboNumber this.add(food) } }, { where: where(), match: ({context, isA}) => isA(context.marker, 'number') && !context.evaluate, apply: async ({context, e}) => { food.comboNumber = { value: (await e(context)).value } this.add(Object.assign(food, context)) } }, { where: where(), match: ({context, isA}) => isA(context.marker, food.marker), apply: ({context}) => { this.add(Object.assign(food, context)) } }, { where: where(), match: ({context, isA}) => isA(context.marker, `${food.marker}_modifier`), apply: ({context}) => { const value = `${context.value}_${food.value}` this.add(Object.assign(food, { value })) } }, ] }, ]) } else { this.api.addAskedForButNotAvailable(food) } return } this.api.add(item) this.api.args.mentioned({ context: food }) for (const addIt of addsInsteadOfModifications) { this.add(addIt) } } } // user ask what the order was show() { this.api.show() } say(response) { this.api.say(response) } } const config = { name: 'fastfood', operators: [ "([orderNoun|order])", "([showOrder|show] ([orderNoun/0]))", ], // flatten: ['list'], // TODO use node naming not python semantics: [ { where: where(), priority: -10, match: ({context}) => context.marker == 'compound_operator', apply: async ({context, s}) => { context.marker = 'list' context.flatten = true await s(context) } }, { where: where(), match: ({context, isAListable}) => isAListable(context, 'edible') && context.marker !== 'edible' && !context.same && !context.isResponse && !context.evaluate, apply: ({context, km, api, instance}) => { for (const element of propertyToArray(context)) { km('fastfood').api.state.add(element) } } }, ], floaters: ['quantity'], bridges: [ { id: 'orderNoun', parents: ['noun', 'queryable'], evaluator: ({context, api}) => { context.evalue = { marker: 'list', value: api.objects.items } api.show() } }, { id: 'showOrder', parents: ['verb'], bridge: "{ ...next(operator), order: after[0] }", generatorp: async ({context, g}) => `show ${await g(context.order)}`, semantic: ({api}) => { api.state.show() }, }, ], } knowledgeModule( { config, includes: [edible, countable, events, sizeable], api: () => new API(), initializer: ({api}) => { api.state = new State(api) }, module, description: 'fastfood related concepts', test: { name: './fastfood.test.json', contents: fastfood_tests, checks: { objects: [ 'show', { property: 'items', filter: [ 'combo', 'pieces', 'size', 'item_id', 'id', { property: 'food', filter: [ 'marker', 'value', 'text' ] }, { property: 'modifications', filter: [ 'id', 'item_id', 'food' ] }, 'needsDrink' ], }, 'changes', 'response', { property: 'notAvailable', filter: [ 'marker', 'value', 'text' ] }, { property: 'notAvailableModification', filter: [ 'marker', 'value', 'text' ] }, { property: 'quantity', filter: ['marker', 'value', 'text' ] }, { property: 'pieces', filter: ['marker', 'value', 'text' ] }, ], context: [ ...defaultContextCheck(), // TODO some kind of conditional selector { match: (value) => value.marker == 'count', filter: ['marker', 'value', 'text'] }, { property: 'comboNumber', filter: ['marker', 'value', 'text' ] }, ], }, }, template: { template, instance: fastfood_instance, }, })