/**
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */ import * as CLM from '@conversationlearner/models' ; import { TreeNode , TreeScorerStep } from '../components/modals/TreeView/TreeNodeLabel' ; export class TreeUtils { private entities : CLM . EntityBase [ ] ; private tree : TreeNode ; static MakeTree ( dialogs , entities ) { const treeUtils = new TreeUtils ( dialogs , entities ) ; return treeUtils . tree ; } constructor ( dialogs , entities ) { this . entities = entities ; this . tree = this . makeRoot ( ) ; let trainDialogs ; if ( dialogs [ 0 ] && ( dialogs [ 0 ] as CLM . LogDialog ) . logDialogId ) { const logDialogs = dialogs as CLM . LogDialog [ ] ; trainDialogs = logDialogs . map ( ld => CLM . ModelUtils . ToTrainDialog ( ld ) ) ; } else { trainDialogs = dialogs as CLM . TrainDialog [ ] ; } trainDialogs . forEach ( td => this . addTrainDialog ( this . tree , td ) ) ; } isInTree ( trainDialog ) { if ( ! this . tree ) { throw new Error ( "Tree has not been created" ) ; } let parent = this . tree ; for ( let [ roundIndex , round ] of trainDialog . rounds . entries ( ) ) { parent = this . findMatchingRound ( parent , round , roundIndex , trainDialog , true ) ; if ( ! parent ) { return false ; } } return true ; } getLeaves ( treeNode ? ) { if ( ! this . tree ) { throw new Error ( "Tree has not been created" ) ; } const curNode = treeNode || this . tree ; let leaves = [ ] ; if ( curNode . children . length === 0 ) { return [ curNode ] ; } curNode . children . forEach ( tn => leaves = [ ... leaves , ... this . getLeaves ( tn ) ] ) ; return leaves ; } getLeafActionIds ( ) { const trainLeaves = this . getLeaves ( ) ; return trainLeaves . filter ( l => l . scorerSteps && l . scorerSteps . length > 0 && l . scorerSteps [ l . scorerSteps . length - 1 ] . actionId ) . map ( l => l . scorerSteps ! [ l . scorerSteps ! . length - 1 ] . actionId ! ) ; } private makeRoot ( ) { return { name : "start" , attributes : undefined , children : [ ] , trainDialogIds : [ ] , sourceLogDialogIds : [ ] } ; } private addExtractorStep ( parentNode , extractorStep , roundIndex , trainDialog ) { const child = { name : "User" , userInput : extractorStep . textVariations . map ( tv => { return { content : tv . text , trainDialogId : trainDialog . trainDialogId } ; } ) , attributes : undefined , children : [ ] , trainDialogIds : trainDialog . trainDialogId ? [ trainDialog . trainDialogId ] : [ ] , sourceLogDialogIds : trainDialog . sourceLogDialogId ? [ trainDialog . sourceLogDialogId ] : [ ] , roundIndex } ; parentNode . children . push ( child ) ; return child ; } private toTreeScorerStep ( scorerStep ) { const entities = [ ] ; if ( scorerStep . input . filledEntities && scorerStep . input . filledEntities . length > 0 ) { scorerStep . input . filledEntities . forEach ( fe => { let entity = this . entities . find ( e => e . entityId === fe . entityId ) ; if ( entity ) { entities . push ( entity . entityName ) ; } else { entities . push ( "UNKNOWN ENTITY" ) ; } } ) ; } return { actionId : scorerStep . labelAction || "UNKNOWN ACTION" , memory : entities } ; } private addScorerStep ( parentNode , scorerStep , roundIndex , scoreIndex , trainDialog ) { if ( ! scorerStep . labelAction ) { return parentNode ; } if ( ! parentNode . scorerSteps ) { parentNode . scorerSteps = [ ] ; } parentNode . scorerSteps . push ( this . toTreeScorerStep ( scorerStep ) ) ; return parentNode ; } private addScorerSteps ( parentNode , scorerSteps , roundIndex , trainDialog ) { let parent = parentNode ; scorerSteps . forEach ( ( scorerStep , scoreIndex ) => { parent = this . addScorerStep ( parent , scorerStep , roundIndex , scoreIndex , trainDialog ) ; } ) ; return parent ; } private doesRoundMatch ( round1 , round2 ) { if ( round1 . scorerSteps && ! round2 . scorerSteps || ! round1 . scorerSteps && round2 . scorerSteps ) { return false ; } else if ( ! round1 . scorerSteps && ! round2 . scorerSteps ) { return true ; } else { return JSON . stringify ( round1 . scorerSteps ) === JSON . stringify ( round2 . scorerSteps ) ; } } private findMatchingRound ( parent , round , roundIndex , trainDialog , testOnly ) { const newParent = this . makeRoot ( ) ; const child = this . addRound ( newParent , round , roundIndex , trainDialog ) ; const newRound = newParent . children [ 0 ] ; const match = parent . children . find ( r => { return this . doesRoundMatch ( r , newRound ) ; } ) ; if ( match ) { match . userInput = [ ... match . userInput , ... newRound . userInput ] ; match . trainDialogIds = [ ... match . trainDialogIds , ... newRound . trainDialogIds ] ; match . sourceLogDialogIds = [ ... match . sourceLogDialogIds , ... newRound . sourceLogDialogIds ] ; match . allowForeignObjects = true ; return match ; } if ( testOnly ) { return null ; } parent . children . push ( newParent . children [ 0 ] ) ; return child ; } private addRound ( parentNode , round , roundIndex , trainDialog ) { let parent = parentNode ; parent = this . addExtractorStep ( parent , round . extractorStep , roundIndex , trainDialog ) ; parent = this . addScorerSteps ( parent , round . scorerSteps , roundIndex , trainDialog ) ; return parent ; } private addTrainDialog ( tree , trainDialog ) { let parent = tree ; for ( let [ roundIndex , round ] of trainDialog . rounds . entries ( ) ) { parent = this . findMatchingRound ( parent , round , roundIndex , trainDialog , false ) ; if ( ! parent ) { return ; } } } }