/**
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */ import * as CLM from '@conversationlearner/models' ; import * as Util from './util' ; import * as DialogUtils from './dialogUtils' ; import * as OBIUtils from './obiUtils' ; import * as BB from 'botbuilder' ; import { SelectionType , User } from '../types' ; import { EditDialogType } from '../types/const' ; export async function onInsertAction ( trainDialog , selectedActivity , isLastActivity , entities , actions , appId , scoreFromTrainDialog , clearWebChatScrollPosition ) { const clData = selectedActivity . channelData . clData ; const roundIndex = clData . roundIndex ?? 0 ; const scoreIndex = clData . scoreIndex ; const definitions = { entities , actions , trainDialogs : [ ] } ; const trainDialogCopy = Util . deepCopy ( trainDialog ) ; trainDialogCopy . definitions = definitions ; trainDialogCopy . rounds = trainDialogCopy . rounds . slice ( 0 , roundIndex + 1 ) ; if ( trainDialogCopy . rounds [ roundIndex ] . scorerSteps . length > 0 && trainDialogCopy . rounds [ roundIndex ] . scorerSteps [ 0 ] . labelAction === undefined ) { trainDialogCopy . rounds [ roundIndex ] . scorerSteps = [ ] ; } else if ( scoreIndex === null ) { trainDialogCopy . rounds [ roundIndex ] . scorerSteps = [ ] ; } else { trainDialogCopy . rounds [ roundIndex ] . scorerSteps = trainDialogCopy . rounds [ roundIndex ] . scorerSteps . slice ( 0 , scoreIndex + 1 ) ; } const uiScoreResponse = await scoreFromTrainDialog ( appId , trainDialogCopy ) ; if ( ! uiScoreResponse . scoreResponse ) { throw new Error ( "Empty Score Response" ) ; } const canEndSession = isLastActivity && ! DialogUtils . hasEndSession ( trainDialog , actions ) ; let insertedAction = DialogUtils . getBestAction ( uiScoreResponse . scoreResponse , actions , canEndSession ) ; if ( ! insertedAction && uiScoreResponse . scoreResponse . unscoredActions [ 0 ] ) { const lastRoundIndex = trainDialogCopy . rounds . length - 1 ; const lastScoreIndex = trainDialogCopy . rounds [ lastRoundIndex ] . scorerSteps . length > 0 ? trainDialogCopy . rounds [ lastRoundIndex ] . scorerSteps . length - 1 : null ; const memories = DialogUtils . getPrevMemories ( trainDialogCopy , entities , lastRoundIndex , lastScoreIndex ) ; insertedAction = DialogUtils . getBestUnscoredAction ( uiScoreResponse . scoreResponse , actions , entities , memories ) ; } if ( ! insertedAction ) { throw new Error ( "No actions available" ) ; } const scorerStep = { logicResult : undefined , input : uiScoreResponse . scoreInput ! , labelAction : insertedAction . actionId , scoredAction : insertedAction } ; const newTrainDialog = Util . deepCopy ( trainDialog ) ; newTrainDialog . definitions = definitions ; const curRound = newTrainDialog . rounds [ roundIndex ] ; if ( curRound . scorerSteps . length === 0 || curRound . scorerSteps [ 0 ] . labelAction === undefined ) { curRound . scorerSteps = [ scorerStep ] ; } else if ( scoreIndex === null ) { curRound . scorerSteps = [ scorerStep , ... curRound . scorerSteps ] ; } else { curRound . scorerSteps . splice ( scoreIndex + 1 , 0 , scorerStep ) ; } if ( roundIndex === trainDialog . rounds . length - 1 && ( scoreIndex === null || scoreIndex === curRound . scorerSteps . length - 1 ) ) { clearWebChatScrollPosition ( ) ; } return newTrainDialog ; } export async function onInsertInput ( trainDialog , selectedActivity , inputText , appId , entities , actions , extractFromTrainDialog , trainDialogReplay , clearWebChatScrollPosition ) { if ( ! inputText ) { throw new Error ( "inputText is null" ) ; } const clData = selectedActivity . channelData . clData ; const roundIndex = clData . roundIndex ! ; const scoreIndex = clData . scoreIndex ?? 0 ; const senderType = clData . senderType ; const definitions = { entities , actions , trainDialogs : [ ] } ; const partialTrainDialog = Util . deepCopy ( trainDialog ) ; partialTrainDialog . definitions = definitions ; partialTrainDialog . rounds = partialTrainDialog . rounds . slice ( 0 , roundIndex + 1 ) ; const lastRound = partialTrainDialog . rounds [ partialTrainDialog . rounds . length - 1 ] ; lastRound . scorerSteps = lastRound . scorerSteps . slice ( 0 , scoreIndex ) ; const userInput = { text : inputText } ; const extractResponse = await extractFromTrainDialog ( appId , partialTrainDialog , userInput ) ; if ( ! extractResponse ) { throw new Error ( "No extract response" ) ; } const textVariations = CLM . ModelUtils . ToTextVariations ( [ extractResponse ] ) ; const extractorStep = { textVariations } ; let newTrainDialog = Util . deepCopy ( trainDialog ) ; newTrainDialog . definitions = definitions ; let scorerSteps ; if ( senderType === CLM . SenderType . User ) { scorerSteps = trainDialog . rounds [ roundIndex ] . scorerSteps ; newTrainDialog . rounds [ roundIndex ] . scorerSteps = [ ] ; } else { scorerSteps = trainDialog . rounds [ roundIndex ] . scorerSteps . slice ( scoreIndex + 1 ) ; newTrainDialog . rounds [ roundIndex ] . scorerSteps . splice ( scoreIndex + 1 , Infinity ) ; } const newRound = { extractorStep , scorerSteps } ; newTrainDialog . rounds . splice ( roundIndex + 1 , 0 , newRound ) ; const replayedDialog = await trainDialogReplay ( appId , newTrainDialog ) ; if ( roundIndex === trainDialog . rounds . length - 1 ) { clearWebChatScrollPosition ( ) ; } return replayedDialog ; } export async function onChangeAction ( trainDialog , selectedActivity , trainScorerStep , editType , appId , entities , actions , lgItems , trainDialogReplay , editActionThunkAsync ) { const clData = selectedActivity . channelData . clData ; const roundIndex = clData . roundIndex ! ; const scoreIndex = clData . scoreIndex ! ; const definitions = { entities , actions , trainDialogs : [ ] } ; const newTrainDialog = Util . deepCopy ( trainDialog ) ; const oldTrainScorerStep = newTrainDialog . rounds [ roundIndex ] . scorerSteps [ scoreIndex ] ; newTrainDialog . rounds [ roundIndex ] . scorerSteps [ scoreIndex ] = trainScorerStep ; newTrainDialog . definitions = definitions ; const replayedDialog = await trainDialogReplay ( appId , newTrainDialog ) ; if ( editType === EditDialogType . IMPORT ) { const replacedAction = actions . find ( a => a . actionId === oldTrainScorerStep . labelAction ) ; let importHash = null ; if ( oldTrainScorerStep . importText ) { const filledEntityIdMap = DialogUtils . filledEntityIdMap ( trainScorerStep . input . filledEntities , entities ) ; const importText = OBIUtils . importTextWithEntityIds ( oldTrainScorerStep . importText , filledEntityIdMap ) ; importHash = CLM . hashText ( importText ) ; } else if ( CLM . ActionBase . isPlaceholderAPI ( replacedAction ) ) { const apiAction = new CLM . ApiAction ( replacedAction as any ) ; importHash = CLM . hashText ( apiAction . name ) ; } if ( importHash ) { const action = actions . find ( a => a . actionId === trainScorerStep . labelAction ) ; if ( action ) { const newAction = Util . deepCopy ( action ) ; if ( ! newAction . clientData ?. actionHashes ) { newAction . clientData = { actionHashes : [ ] } ; } if ( oldTrainScorerStep . importText && lgItems ) { const lgHash = CLM . hashText ( oldTrainScorerStep . importText ) ; const lgItem = lgItems . find ( lg => lg . hash === lgHash ) ; if ( lgItem ) { newAction . clientData . lgName = lgItem . lgName ; } } newAction . clientData . actionHashes ! . push ( importHash ) ; await editActionThunkAsync ( appId , newAction ) ; OBIUtils . replaceImportActions ( replayedDialog , [ ... actions , newAction ] , entities ) ; } } else { OBIUtils . replaceImportActions ( replayedDialog , actions , entities ) ; } } return replayedDialog ; } export async function onChangeExtraction ( trainDialog , selectedActivity , textVariations , editType , appId , entities , actions , trainDialogReplay ) { const clData = selectedActivity . channelData . clData ; const roundIndex = clData . roundIndex ! ; const definitions = { entities , actions , trainDialogs : [ ] } ; const newTrainDialog = Util . deepCopy ( trainDialog ) ; newTrainDialog . definitions = definitions ; newTrainDialog . rounds [ roundIndex ] . extractorStep . textVariations = textVariations ; const replayedDialog = await trainDialogReplay ( appId , newTrainDialog ) ; if ( editType === EditDialogType . IMPORT ) { OBIUtils . replaceImportActions ( replayedDialog , actions , entities ) ; } return replayedDialog ; } export async function onDeleteTurn ( trainDialog , selectedActivity , appId , entities , actions , trainDialogReplay ) { const clData = selectedActivity . channelData . clData ; const senderType = clData . senderType ; const roundIndex = clData . roundIndex ! ; const scoreIndex = clData . scoreIndex ; const newTrainDialog = Util . deepCopy ( trainDialog ) ; newTrainDialog . definitions = { entities , actions , trainDialogs : [ ] } ; const curRound = newTrainDialog . rounds [ roundIndex ] ; if ( senderType === CLM . SenderType . User ) { if ( roundIndex > 0 ) { const previousRound = newTrainDialog . rounds [ roundIndex - 1 ] ; previousRound . scorerSteps = [ ... previousRound . scorerSteps , ... curRound . scorerSteps ] ; previousRound . scorerSteps = previousRound . scorerSteps . filter ( ss => ss . labelAction !== undefined ) ; } newTrainDialog . rounds . splice ( roundIndex , 1 ) ; } else { if ( scoreIndex === null ) { throw new Error ( "Unexpected null scoreIndex" ) ; } curRound . scorerSteps . splice ( scoreIndex , 1 ) ; } return trainDialogReplay ( appId , newTrainDialog ) ; } export async function onReplayTrainDialog ( trainDialog , appId , entities , actions , trainDialogReplay ) { const newTrainDialog = Util . deepCopy ( trainDialog ) ; newTrainDialog . definitions = { entities , actions , trainDialogs : [ ] } ; if ( trainDialog . validity === CLM . Validity . WARNING || trainDialog . validity === CLM . Validity . UNKNOWN ) { newTrainDialog . validity = CLM . Validity . VALID ; } return trainDialogReplay ( appId , newTrainDialog ) ; } export async function onUpdateActivities ( newTrainDialog , selectedActivity , selectionType , appId , user , fetchActivitiesAsync ) { const teachWithActivities = await fetchActivitiesAsync ( appId , newTrainDialog , user . name , user . id ) ; let activityIndex = null ; if ( selectedActivity !== null ) { activityIndex = selectedActivity ? DialogUtils . matchedActivityIndex ( selectedActivity , teachWithActivities . activities ) : null ; if ( activityIndex !== null && selectionType === SelectionType . NEXT ) { activityIndex = activityIndex + 1 ; } else if ( activityIndex === null && selectionType === SelectionType . CURRENT ) { const clData = selectedActivity . channelData . clData ; if ( clData ?. activityIndex ) { activityIndex = clData . activityIndex ; } } else if ( selectionType === SelectionType . NONE ) { activityIndex = null ; } } return { teachWithActivities , activityIndex } ; } export interface EditHandlerArgs { userInput ? : string ; extractResponse ? : CLM . ExtractResponse ; textVariations ? : CLM . TextVariation [ ] ; trainScorerStep ? : CLM . TrainScorerStep ; selectionType ? : SelectionType ; isLastActivity ? : boolean ; } export async function onEditTeach ( activityIndex , args , tags , description , editHandler , teachSession , app , user , actions , entities , fetchTrainDialogAsync , deleteTeachSessionAsync , fetchActivitiesAsync ) { const trainDialog = await fetchTrainDialogAsync ( app . appId , teachSession . trainDialogId , false ) ; trainDialog . tags = tags ; trainDialog . description = description ; trainDialog . definitions = { entities : entities , actions : actions , trainDialogs : [ ] } ; await deleteTeachSessionAsync ( teachSession , app ) ; const teachWithActivities = await fetchActivitiesAsync ( app . appId , trainDialog , user . name , user . id ) ; const selectedActivity = activityIndex != null ? teachWithActivities . activities [ activityIndex ] : teachWithActivities . activities [ teachWithActivities . activities . length - 1 ] ; const clData = { ... selectedActivity . channelData . clData , activityIndex } ; selectedActivity . channelData . clData = clData ; await editHandler ( trainDialog , selectedActivity , args ) ; } /**
 * Returns placeholder if it exists, otherwise creates it if given creation action.
 * Will add the new action to `actions` if one is created.
 */ export async function getOrCreatePlaceholderAPIAction ( appId , placeholderName , isTerminal , actions , createActionThunkAsync ) { const apiHash = CLM . hashText ( placeholderName ) ; let placeholder = actions . find ( a => a . clientData ?. actionHashes ?. find ( h => h === apiHash ) !== undefined ) ; if ( ! placeholder ) { placeholder = actions . filter ( a => CLM . ActionBase . isPlaceholderAPI ( a ) ) . map ( aa => new CLM . ApiAction ( aa ) ) . find ( aaa => aaa . name === placeholderName ) ; } if ( placeholder ) { return placeholder ; } if ( createActionThunkAsync ) { const newPlaceholder = CLM . ActionBase . createPlaceholderAPIAction ( placeholderName , isTerminal ) ; newPlaceholder . clientData = { actionHashes : [ apiHash ] } ; const newAction = await createActionThunkAsync ( appId , newPlaceholder ) ; if ( ! newAction ) { throw new Error ( "Failed to create placeholder API" ) ; } actions . push ( newAction ) ; return newAction ; } return undefined ; } export function scorerStepFromActivity ( trainDialog , selectedActivity ) { if ( ! selectedActivity ) { return undefined ; } const clData = selectedActivity . channelData . clData ; if ( clData . roundIndex !== null ) { const round = trainDialog . rounds [ clData . roundIndex ] ; if ( round . scorerSteps . length > 0 ) { if ( typeof clData . scoreIndex === "number" ) { return round . scorerSteps [ clData . scoreIndex ] ; } return round . scorerSteps [ 0 ] ; } } return undefined ; } export async function getAPIPlaceholderScorerStep ( placeholderName , isTerminal , appId , actions , filledEntityMap , createActionThunkAsync ) { const placeholderAction = await getOrCreatePlaceholderAPIAction ( appId , placeholderName , isTerminal , actions , createActionThunkAsync ) ; if ( ! placeholderAction ) { throw new Error ( "Unable to create API placeholder Action" ) ; } const filledEntities = filledEntityMap . FilledEntities ( ) ; let scoredAction = { actionId : placeholderAction . actionId , payload : placeholderAction . payload , isTerminal : placeholderAction . isTerminal , actionType : CLM . ActionTypes . API_LOCAL , score : 1 } ; let scoreInput = { filledEntities : [ ] , context : { } , maskedActions : [ ] } ; let logicResult = { logicValue : undefined , changedFilledEntities : filledEntities , } ; return { input : scoreInput , labelAction : placeholderAction . actionId , logicResult , scoredAction : scoredAction } ; }