ekms 7.12.4 → 7.12.5-beta.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -30,6 +30,8 @@ const fastfood_instance = require('./fastfood.instance.json')
30
30
  double hamburger
31
31
  number 1 and 2
32
32
  number 1 2 and 3
33
+ combo 1 through 5
34
+
33
35
  */
34
36
 
35
37
  const template = {
@@ -160,7 +162,6 @@ const template = {
160
162
  { "context": [['smoothie_ingredient', 1], ['list', 0], ['smoothie_ingredient', 1], ['smoothie', 1]], ordered: true, choose: [1] },
161
163
 
162
164
  { "context": [['list', 0], ['number', 1], ['combo', 1], ['number', 1]], ordered: true, choose: [2,3] },
163
- { "context": [['withModification', 0], ['modification', 1], ['list', 0], ['modification', 1]], ordered: true, choose: [2] },
164
165
 
165
166
 
166
167
  { context: [['comboNumber', 0], ['counting',0]], choose: [0] },
@@ -224,7 +225,7 @@ const template = {
224
225
  {
225
226
  id: 'withModification',
226
227
  level: 0,
227
- isA: ['preposition'],
228
+ before: ['preposition'],
228
229
  generatorp: ({context, gp}) => `with ${gp(context.modifications)}`,
229
230
  bridge: "{ ...next(operator), modifications: after[0], flatten: false }",
230
231
  },
@@ -250,7 +251,7 @@ const template = {
250
251
  {
251
252
  id: 'comboNumber',
252
253
  convolution: true,
253
- before: ['combo'],
254
+ before: ['combo', 'preposition'],
254
255
  bridge: "{ ...next(before[0]), postModifiers: append(before[0].postModifiers, ['comboNumber']), comboNumber: after[0], instance: true, flatten: true }",
255
256
  },
256
257
  {
@@ -297,17 +298,24 @@ const template = {
297
298
  const naArray = api.getAskedForButNotAvailable()
298
299
  naArray.forEach((f) => f.paraphrase = true)
299
300
  const naContext = toContext(naArray)
300
- /*
301
- naContext.isResponse = true
302
- naContext.marker = 'verbatim'
303
- naContext.verbatim = `The following are not menu items: ${gp(naContext)}`
304
- insert(naContext)
305
- */
306
301
  verbatim(`The following are not menu items: ${gp(naContext)}`)
307
302
  // allow other motivation to run
308
303
  context.cascade = true
309
304
  }
310
305
  },
306
+ {
307
+ where: where(),
308
+ match: ({context, api}) => context.marker == 'controlEnd' && api.hasAskedForButNotAvailableModification(),
309
+ apply: ({context, api, gp, toContext, verbatim}) => {
310
+ const naArray = api.getAskedForButNotAvailableModification().map(({ item, modification }) => {
311
+ // return `${gp(item)} can not be modified with ${gp(modification)}.`
312
+ return `XXX can not be modified with ${gp(modification)}.`
313
+ })
314
+ verbatim(naArray.join(' '))
315
+ // allow other motivation to run
316
+ context.cascade = true
317
+ }
318
+ },
311
319
  ]
312
320
  },
313
321
  ({ask, api}) => {
@@ -342,17 +350,35 @@ const template = {
342
350
  {
343
351
  where: where(),
344
352
  oneShot: false,
345
- matchq: (args) => askAbout(args).length > 0,
353
+ onNevermind: ({verbatim, ...args}) => {
354
+ // this is cross km boundaries from the dialogues km to this one so the api if for dialogs.
355
+ // i need to get the one for fastfood here.
356
+ const api = args.kms.fastfood.api
357
+ const needsDrink = askAbout({ args, api })
358
+ for (const item of needsDrink) {
359
+ api.remove(item)
360
+ }
361
+ },
362
+ matchq: (args) => askAbout(args).length > 0 && args.context.marker == 'controlEnd',
346
363
  applyq: (args) => {
347
364
  args.context.cascade = true
348
365
  const needsDrink = askAbout(args)
366
+ // const details = args.gp({ marker: 'list', value: needsDrink.map((item) => item.food)})
349
367
  if (needsDrink.length > 1) {
350
368
  return `What drinks do you want?`
351
369
  } else {
352
370
  return `What drink do you want?`
353
371
  }
354
372
  },
355
- matchr: (args) => args.isA(args.context.marker, 'drink') && askAbout(args).length > 0,
373
+ matchr: (args) => {
374
+ if (args.isA(args.context.marker, 'drink') && askAbout(args).length > 0) {
375
+ const needsDrink = askAbout(args)
376
+ if (args.api.isAvailableModification(needsDrink[0].food, { ...args.context , id: args.context.value })) {
377
+ return true
378
+ }
379
+ }
380
+ return false
381
+ },
356
382
  applyr: (args) => {
357
383
  // TODO check for is available for all modifications
358
384
  const needsDrink = askAbout(args)
@@ -375,6 +401,33 @@ const template = {
375
401
  ])
376
402
  },
377
403
  {
404
+ operators: [
405
+ "([change] (meal/* || drink/*) (to/1))",
406
+ ],
407
+ hierarchy: [
408
+ ['meal', 'toAble'],
409
+ ['drink', 'toAble'],
410
+ ],
411
+ bridges: [
412
+ {
413
+ id: "change",
414
+ isA: ['verby'],
415
+ localHierarchy: [ ['thisitthat', 'meal'] ],
416
+ generatorp: ({context, gp}) => `change ${gp(context.from)} to ${gp(context.to)}`,
417
+ bridge: "{ ...next(operator), from: after[0], to: after[1].toObject }",
418
+ semantic: ({context, api, e}) => {
419
+ const state = api.state
420
+ const eFrom = e(context.from).evalue
421
+ const from = state.getIdCombo(eFrom.fromSTM ? eFrom : context.from)
422
+ const to = state.getIdCombo(context.to)
423
+ for (const item of api.items()) {
424
+ if (item.id == from.id) {
425
+ api.modify(item, { id: to.id, food: context.to })
426
+ }
427
+ }
428
+ }
429
+ },
430
+ ],
378
431
  priorities: [
379
432
  { context: [['combo', 0], ['number',1], ['list', 0], ['combo', 0]], ordered: true, choose: [0,1] },
380
433
  { context: [['list', 0], ['combo',0], ['number',1]], ordered: true, choose: [1,2] },
@@ -382,6 +435,41 @@ const template = {
382
435
  { context: [['mango', 0], ['passion',0], ['list', 0]], ordered: true, choose: [0,1] },
383
436
  { context: [['number', 1], ['mango_passion',1], ['list', 0]], ordered: true, choose: [0,1] },
384
437
  { context: [['mango', 0], ['mango_passion',0], ['passion',0], ['list', 0]], ordered: true, choose: [0,1,2] },
438
+
439
+ { context: [['drink', 0], ['list',0], ['combo',0], ['number', 0]], ordered: true, choose: [2,3] },
440
+ { context: [['drink', 1], ['list',0], ['combo',0], ['number', 0]], ordered: true, choose: [2,3] },
441
+ { context: [['withModification', 0], ['modification', 1], ['list', 0], ['modification', 1]], ordered: true, choose: [2] },
442
+ { context: [['withModification', 0], ['modification', 1], ['list', 0], ['combo', 1]], ordered: true, choose: [0] },
443
+ { context: [['combo', 2], ['list', 0], ['combo', 1], ['withModification', 1]], ordered: true, choose: [3] },
444
+ ],
445
+ },
446
+ {
447
+ operators: [
448
+ "([remove|remove,delete,drop,ditch,forget,no] (food/*))",
449
+ "([reset|reset,restart,clear])",
450
+ ],
451
+ bridges: [
452
+ {
453
+ id: 'remove',
454
+ isA: ['verby'],
455
+ bridge: "{ ...next(operator), remove: after[0], postModifiers: ['remove'] }",
456
+ semantic: ({context, api}) => {
457
+ const state = api.state
458
+ for (const item of api.items()) {
459
+ if (state.match(context.remove, item)) {
460
+ api.remove(item)
461
+ }
462
+ }
463
+ }
464
+ },
465
+ {
466
+ id: 'reset',
467
+ isA: ['verby'],
468
+ bridge: "{ ...next(operator) }",
469
+ semantic: ({context, api}) => {
470
+ api.reset()
471
+ }
472
+ },
385
473
  ],
386
474
  },
387
475
  ],
@@ -390,35 +478,66 @@ const template = {
390
478
  class API {
391
479
  initialize({ objects, config }) {
392
480
  this._objects = objects
393
- this._objects.items = []
394
- this._objects.notAvailable = []
481
+ this.reset()
395
482
  }
396
483
 
397
484
  show() {
398
485
  this._objects.show = this._objects.items
399
486
  }
400
487
 
488
+ toItem(item_id) {
489
+ if (Array.isArray(item_id)) {
490
+ return this._objects.items[item_id[0]].modifications[item_id[1]]
491
+ } else {
492
+ return this._objects.items[item_id]
493
+ }
494
+ }
495
+
496
+ new_item_id() {
497
+ const item_id = this._objects.item_id_counter
498
+ this._objects.item_id_counter += 1
499
+ return item_id
500
+ }
501
+
401
502
  // returns an item id so things can be updated if needed
402
503
  add(item) {
403
- item.item_id = this._objects.items.length
504
+ item.item_id = this.new_item_id()
404
505
  if (!item.modifications) {
405
506
  item.modifications = []
406
507
  }
508
+ item.modifications.forEach((modification, index) => {
509
+ modification.item_id = [item.item_id, index]
510
+ })
407
511
  this._objects.items.push(item)
408
- return item.item_id
512
+ }
513
+
514
+ reset() {
515
+ this._objects.items = []
516
+ this._objects.notAvailable = []
517
+ this._objects.notAvailableModification = []
518
+ this._objects.item_id_counter = 0
409
519
  }
410
520
 
411
521
  get(item_id) {
412
- return this._objects.items[item_id]
522
+ return this.toItem(item_id)
523
+ }
524
+
525
+ modify(item, changes) {
526
+ Object.assign(this.toItem(item.item_id), changes)
527
+ }
528
+
529
+ remove(item) {
530
+ this._objects.items = this._objects.items.filter( (i) => i.item_id !== item.item_id )
413
531
  }
414
532
 
415
533
  items() {
416
- return this._objects.items
534
+ return [...this._objects.items]
417
535
  }
418
536
 
419
537
  addDrink(item_id, drink) {
420
- this._objects.items[item_id].modifications.push(drink)
421
- this._objects.items[item_id].needsDrink = false
538
+ const item = this.toItem(item_id)
539
+ item.modifications.push(drink)
540
+ item.needsDrink = false
422
541
  }
423
542
 
424
543
  say(response) {
@@ -430,17 +549,40 @@ class API {
430
549
  return this._objects.notAvailable.length > 0
431
550
  }
432
551
 
552
+ hasAskedForButNotAvailableModification(item) {
553
+ return this._objects.notAvailableModification.length > 0
554
+ }
555
+
433
556
  getAskedForButNotAvailable(item) {
434
557
  const na = this._objects.notAvailable
435
558
  this._objects.notAvailable = []
436
559
  return na
437
560
  }
438
561
 
562
+ getAskedForButNotAvailableModification(item) {
563
+ const na = this._objects.notAvailableModification
564
+ this._objects.notAvailableModification = []
565
+ return na
566
+ }
567
+
439
568
  addAskedForButNotAvailable(item) {
440
569
  this._objects.notAvailable.push(item)
441
570
  }
442
571
 
572
+ addAskedForButNotAvailableModification(item, modification) {
573
+ this._objects.notAvailableModification.push({item, modification})
574
+ }
575
+
576
+ isAvailableModification(food, modification) {
577
+ if (this.isAvailable(modification)) {
578
+ if (this.args.isA(modification.id, 'fry') || this.args.isA(modification.id, 'pop')) {
579
+ return true
580
+ }
581
+ }
582
+ }
583
+
443
584
  isAvailable(item) {
585
+ item.id = item.id || item.value
444
586
  if (item.id == 'chicken_nugget') {
445
587
  if (![4,5,6,10].includes(item.pieces)) {
446
588
  return false
@@ -468,6 +610,10 @@ class API {
468
610
  item.id = 'coca_cola'
469
611
  }
470
612
 
613
+ if (item.id == 'fry') {
614
+ item.id = 'french_fry'
615
+ }
616
+
471
617
  return [
472
618
  "hamburger",
473
619
  "cheeseburger",
@@ -558,6 +704,27 @@ class API {
558
704
  }
559
705
  return map[number]
560
706
  }
707
+
708
+ canBeCombo(id) {
709
+ return this.getComboNumber(id) > 0
710
+ }
711
+
712
+ getComboNumber(id) {
713
+ const combos = [
714
+ 'single',
715
+ 'double',
716
+ 'triple',
717
+ 'baconator',
718
+ 'bacon_deluxe',
719
+ 'spicy',
720
+ 'homestyle',
721
+ 'asiago_range_chicken_club',
722
+ 'ultimate_chicken_grill',
723
+ 'chicken_nugget',
724
+ 'premium_cod',
725
+ ]
726
+ return combos.findIndex((e) => e == id) + 1
727
+ }
561
728
  }
562
729
 
563
730
  const api = new API()
@@ -567,17 +734,13 @@ class State {
567
734
  this.api = api
568
735
  }
569
736
 
570
- add(food) {
571
- let quantity = 1
572
- if (food.quantity) {
573
- quantity = food.quantity.value
574
- }
737
+ getIdCombo(food) {
575
738
  let id, combo
576
739
  if (food.comboNumber?.marker == 'numberNumberCombo') {
577
740
  id = this.api.getCombo(food.comboNumber.comboNumber.value)
578
741
  if (!id) {
579
742
  this.api.addAskedForButNotAvailable(food)
580
- return
743
+ return { done: true }
581
744
  }
582
745
  combo = true
583
746
  }
@@ -585,11 +748,11 @@ class State {
585
748
  id = this.api.getCombo(food.comboNumber.value)
586
749
  if (!id) {
587
750
  this.api.addAskedForButNotAvailable(food)
588
- return
751
+ return { done: true }
589
752
  }
590
753
  combo = true
591
754
  } else if (food.marker == 'combo') {
592
- id = food.type.value
755
+ id = food.type?.value
593
756
  combo = true
594
757
  } else {
595
758
  id = food.value
@@ -600,6 +763,32 @@ class State {
600
763
  id = 'chicken_nugget'
601
764
  }
602
765
 
766
+ return { id, combo }
767
+ }
768
+
769
+ match(pattern, item) {
770
+ Object.assign(pattern, this.getIdCombo(pattern))
771
+ if (pattern.id == item.id) {
772
+ return true
773
+ }
774
+ if (!pattern.id) {
775
+ if (pattern.combo == item.combo) {
776
+ return true
777
+ }
778
+ }
779
+ }
780
+
781
+ add(food) {
782
+ let quantity = 1
783
+ if (food.quantity) {
784
+ quantity = food.quantity.value
785
+ }
786
+
787
+ const { id, combo, done } = this.getIdCombo(food)
788
+ if (done) {
789
+ return
790
+ }
791
+
603
792
  const addSize = (item, data) => {
604
793
  if (item.size) {
605
794
  data.size = item.size.value
@@ -607,15 +796,39 @@ class State {
607
796
  return data
608
797
  }
609
798
 
799
+ const getAvailableChildren = (item) => {
800
+ // see if this is a categories of items
801
+ const descendants = this.api.args.hierarchy.descendants(item.id)
802
+ const available = []
803
+ for (const descendant of descendants) {
804
+ if (this.api.isAvailable({ id: descendant})) {
805
+ available.push(descendant)
806
+ }
807
+ }
808
+ return available
809
+ }
810
+
610
811
  let modifications
812
+ const addsInsteadOfModifications = []
611
813
  if (food.modifications) {
612
814
  modifications = []
613
815
  for (const modification of propertyToArray(food.modifications.modifications)) {
614
816
  if (modification.size) {
615
817
  food.size = modification.size
616
818
  }
617
- addSize(modification, { id: modification.value })
618
- modifications.push(addSize(modification, { id: modification.value }))
819
+
820
+ // if not a modification treat as top level request
821
+ if (!this.api.isAvailableModification(food, { ...modification, id: modification.value })) {
822
+ if (this.api.isAvailable(modification)) {
823
+ //this.add(modification)
824
+ addsInsteadOfModifications.push(modification)
825
+ } else {
826
+ this.api.addAskedForButNotAvailable(modification)
827
+ }
828
+ } else {
829
+ addSize(modification, { id: modification.value })
830
+ modifications.push(addSize(modification, { id: modification.value }))
831
+ }
619
832
  }
620
833
  }
621
834
 
@@ -632,46 +845,82 @@ class State {
632
845
  for (let i = 0; i < quantity; ++i) {
633
846
  const item = addSize(food, { id, combo, modifications, pieces, food })
634
847
  if (!this.api.isAvailable(item)) {
635
- this.api.addAskedForButNotAvailable(food)
636
- return
637
- }
638
-
639
- const item_id = this.api.add(item)
640
-
641
- if (false) {
642
- // see if followup for drink is needed
643
-
644
- const hasDrink = (item_id) => {
645
- const item = this.api.get(item_id)
646
- let hasDrink = false
647
- for (let modification of (item.modifications || [])) {
648
- if (!this.api.args.isA(modification.id, 'drink')) {
649
- hasDrink = true
650
- break
651
- }
848
+ const available = []
849
+ for (const descendant of this.api.args.hierarchy.descendants(food.marker)) {
850
+ if (this.api.isAvailable({ id: descendant })) {
851
+ available.push(descendant)
652
852
  }
653
- return hasDrink
654
- }
655
- const needsDrink = (item_id) => {
656
- const item = this.api.get(item_id)
657
- return item.needsDrink
658
853
  }
659
854
 
660
- if (!hasDrink(item_id) && needsDrink(item_id)) {
855
+ // this sentence runs but it doesnt setup the hierarchy: 'combo 1, 2, 3, 4, 5, 6, 7, 9, 10, and 11 are combos'
856
+ // i made a wrong design choice when i setup the phrase 'combo 1 etc'. I should have mapped that to the 'single_combo'
857
+ // but instead had it be a combo with a comboNumber property. That means the language layer doesnt know about the mapping
858
+ // 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
859
+ // demoes to write so i am not fixin that and instead will do || is a combo
860
+
861
+ if (available.length > 0 || food.marker == 'combo') {
661
862
  this.api.args.ask([
662
- {
663
- where: where(),
664
- matchq: ({objects}) => !hasDrink(item_id) && needsDrink(item_id),
665
- applyq: () => `What drink do you want?`,
666
- matchr: ({context, isA}) => isA(context.marker, 'drink'),
667
- applyr: ({context, objects, api}) => {
668
- // TODO check for is available for all modifications
669
- this.api.addDrink(item_id, { id: context.value })
670
- }
863
+ {
864
+ where: where(),
865
+ oneShot: true,
866
+ matchq: ({context}) => context.marker == 'controlEnd',
867
+ applyq: () => {
868
+ // args.context.cascade = true
869
+ const word = food.word
870
+ return `What kind of ${word}?`
671
871
  },
672
- ]
673
- )
872
+ semanticsr: [
873
+ // stuipid hack one because i didnt put combo fully into the NLI layer
874
+ {
875
+ where: where(),
876
+ match: ({context, callId, isA, api}) => {
877
+ return api.canBeCombo(context.marker)
878
+ },
879
+ apply: ({context}) => {
880
+ const comboNumber = {
881
+ value: api.getComboNumber(context.marker)
882
+ }
883
+ food.comboNumber = comboNumber
884
+ this.add(food)
885
+ }
886
+ },
887
+ {
888
+ where: where(),
889
+ match: ({context, isA}) => isA(context.marker, 'number') && !context.evaluate,
890
+ apply: ({context, e}) => {
891
+ food.comboNumber = { value: e(context).value }
892
+ this.add(Object.assign(food, context))
893
+ }
894
+ },
895
+ {
896
+ where: where(),
897
+ match: ({context, isA}) => isA(context.marker, food.marker),
898
+ apply: ({context}) => {
899
+ this.add(Object.assign(food, context))
900
+ }
901
+ },
902
+ {
903
+ where: where(),
904
+ match: ({context, isA}) => isA(context.marker, `${food.marker}_modifier`),
905
+ apply: ({context}) => {
906
+ const value = `${context.value}_${food.value}`
907
+ this.add(Object.assign(food, { value }))
908
+ }
909
+ },
910
+ ]
911
+ },
912
+ ])
913
+ } else {
914
+ this.api.addAskedForButNotAvailable(food)
674
915
  }
916
+ return
917
+ }
918
+
919
+ this.api.add(item)
920
+ this.api.args.mentioned(food)
921
+
922
+ for (const addIt of addsInsteadOfModifications) {
923
+ this.add(addIt)
675
924
  }
676
925
  }
677
926
  }
@@ -686,7 +935,7 @@ class State {
686
935
  }
687
936
  }
688
937
 
689
- const createConfig = () => {
938
+ const createConfig = (additionalConfig) => {
690
939
  const config = new Config({
691
940
  name: 'fastfood',
692
941
  operators: [
@@ -708,7 +957,7 @@ const createConfig = () => {
708
957
  },
709
958
  {
710
959
  where: where(),
711
- match: ({context, isAListable}) => isAListable(context, 'edible') && context.marker !== 'edible' && !context.same && !context.isResponse,
960
+ match: ({context, isAListable}) => isAListable(context, 'edible') && context.marker !== 'edible' && !context.same && !context.isResponse && !context.evaluate,
712
961
  apply: ({context, km, api, instance}) => {
713
962
  for (const element of propertyToArray(context)) {
714
963
  km('fastfood').api.state.add(element)
@@ -743,6 +992,9 @@ const createConfig = () => {
743
992
  config.initializer( ({api}) => {
744
993
  api.state = new State(api)
745
994
  })
995
+ if (additionalConfig) {
996
+ additionalConfig(config)
997
+ }
746
998
  config.restart_auto_rebuild()
747
999
  return config
748
1000
  }
@@ -751,16 +1003,30 @@ knowledgeModule( {
751
1003
  module,
752
1004
  description: 'fastfood related concepts',
753
1005
  createConfig,
1006
+ acceptsAdditionalConfig: true,
754
1007
  test: {
755
1008
  name: './fastfood.test.json',
756
1009
  contents: fastfood_tests,
757
1010
  checks: {
758
1011
  objects: [
759
1012
  'show',
760
- { property: 'items', filter: ['combo', 'pieces', 'size', 'item_id', 'id', 'modifications', 'needsDrink'] },
1013
+ {
1014
+ property: 'items',
1015
+ filter: [
1016
+ 'combo',
1017
+ 'pieces',
1018
+ 'size',
1019
+ 'item_id',
1020
+ 'id',
1021
+ { property: 'food', filter: [ 'marker', 'value', 'text' ] },
1022
+ { property: 'modifications', filter: [ 'id', 'item_id', 'food' ] },
1023
+ 'needsDrink'
1024
+ ],
1025
+ },
761
1026
  'changes',
762
1027
  'response',
763
1028
  { property: 'notAvailable', filter: [ 'marker', 'value', 'text' ] },
1029
+ { property: 'notAvailableModification', filter: [ 'marker', 'value', 'text' ] },
764
1030
  { property: 'quantity', filter: ['marker', 'value', 'text' ] },
765
1031
  { property: 'pieces', filter: ['marker', 'value', 'text' ] },
766
1032
  ],