clwoz-models
Version:
Models for ConversationLearner
278 lines (240 loc) • 8.25 kB
text/typescript
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { MemoryValue, Memory } from './Memory'
import { ModelUtils } from './ModelUtils'
import { EntityBase } from './Entity'
export interface FilledEntity {
entityId: string | null
values: MemoryValue[]
}
export const filledEntityValueAsString = (fe: FilledEntity): string => memoryValuesAsString(fe.values)
export const memoryValuesAsString = (memories: MemoryValue[]): string => {
// Print out list in friendly manner
let group = ''
for (let key in memories) {
let index = +key
let prefix = ''
if (memories.length !== 1 && index === memories.length - 1) {
prefix = ' and '
} else if (index !== 0) {
prefix = ', '
}
let value = memories[key]
let text = value.displayText ? value.displayText : value.userText
group += `${prefix}${text}`
}
return group
}
// In future will refactor to remove FilledEntityMap, but this method provides abstraction
// or isolation to methods that would normally consume filledEntityMap to reduce coupling
// and make code more flexible
export const getEntityDisplayValueMap = (filledEntityMap: FilledEntityMap): Map<string, string> => {
return Object.keys(filledEntityMap.map).reduce((m, key) => {
const entityDisplayValue = filledEntityMap.ValueAsString(key)
if (entityDisplayValue) {
m.set(key, entityDisplayValue)
}
// Entity is missing. Provide readable error message
else {
// Default to showing key
let missingValue = key
// If key is an entityId, try to get value from name key
if (filledEntityMap.map[key].entityId === key) {
for (let k of Object.keys(filledEntityMap.map)) {
if (k !== key && filledEntityMap.map[k].entityId === key) {
missingValue = k
}
}
}
m.set(key, `[$${missingValue}]`)
}
return m
}, new Map<string, string>())
}
export class FilledEntityMap {
public map: { [key: string]: FilledEntity } = {}
public constructor(init?: Partial<FilledEntityMap>) {
Object.assign(this, init)
}
public static FromFilledEntities(filledEntities: FilledEntity[], entities: EntityBase[]): FilledEntityMap {
let filledEntityMap = new FilledEntityMap()
for (let filledEntity of filledEntities) {
let entity = entities.find(e => e.entityId === filledEntity.entityId)
if (entity) {
filledEntityMap.map[entity.entityName] = filledEntity
}
}
return filledEntityMap
}
// Flips entity name based map to entityId based map
public EntityMapToIdMap() {
let filledEntityMap = new FilledEntityMap()
for (let entityName in this.map) {
const filledEntity = this.map[entityName]
filledEntityMap.map[filledEntity.entityId!] = filledEntity
}
return filledEntityMap
}
// Provided an update (diff), update the filled entity list
public UpdateFilledEntities(filledEntities: FilledEntity[], entities: EntityBase[]) {
for (let filledEntity of filledEntities) {
let entity = entities.find(e => e.entityId === filledEntity.entityId)
if (entity) {
this.map[entity.entityName] = filledEntity
}
}
}
public ToMemory(): Memory[] {
let memory: Memory[] = []
for (let entityName in this.map) {
let entityValues = this.map[entityName] ? this.map[entityName].values : []
memory.push({ entityName: entityName, entityValues })
}
return memory
}
public ValueAsList(entityName: string): string[] {
if (!this.map[entityName]) {
return []
}
return this.map[entityName].values.filter(v => typeof v.userText === 'string').map(v => v.userText!)
}
public ValueAsString(entityName: string): string | null {
if (!this.map[entityName]) {
return null
}
if (this.map[entityName].values.length === 0) {
return null
}
// Print out[] list in friendly manner
return filledEntityValueAsString(this.map[entityName])
}
public ValueAsNumber(entityName: string): number | null {
const textObj = this.ValueAsString(entityName)
let number = Number(textObj)
if (isNaN(number)) {
return null
}
return number
}
public ValueAsBoolean(entityName: string): boolean | null {
const textObj = this.ValueAsString(entityName)
if (textObj) {
if (textObj.toLowerCase() === 'true') {
return true
}
if (textObj.toLowerCase() === 'false') {
return false
}
}
return null
}
public ValueAsObject<T>(entityName: string): T | null {
const textObj = this.ValueAsString(entityName)
if (textObj) {
return JSON.parse(textObj) as T
}
return null
}
public Values(entityName: string): MemoryValue[] {
if (!this.map[entityName]) {
return []
}
return this.map[entityName].values
}
public Forget(entityName: string, entityValue: string | null = null, isBucket: boolean = false): void {
// Check if entity buckets values
if (isBucket) {
// Entity might not be in memory
if (!this.map[entityName]) {
return
}
// If no entity Value provide, clear the entity
if (!entityValue) {
delete this.map[entityName]
} else {
// Find case insensitive index
let lowerCaseNames = this.map[entityName].values.filter(mv => mv.userText).map(mv => mv.userText!.toLowerCase())
let index = lowerCaseNames.indexOf(entityValue.toLowerCase())
if (index > -1) {
this.map[entityName].values.splice(index, 1)
if (this.map[entityName].values.length === 0) {
delete this.map[entityName]
}
}
}
} else {
delete this.map[entityName]
}
}
// Remember multiple values for an entity
public RememberMany(
entityName: string,
entityId: string,
entityValues: string[],
isBucket: boolean = false,
builtinType: string | null = null,
resolution: {} | null = null
): void {
// Filter out empty strings
const values = entityValues.filter(s => s !== "")
// If no non-empty values, acts as delete
if (values.length === 0) {
this.Forget(entityName)
return
}
for (let entityValue of values) {
this.Remember(entityName, entityId, entityValue, isBucket, builtinType, resolution)
}
}
// Remember value for an entity
public Remember(
entityName: string,
entityId: string,
entityValue: string,
isBucket: boolean = false,
builtinType: string | null = null,
resolution: any | null = null,
enumValueId: string | undefined = undefined
): void {
// If setting to an empty string is actually a delete
if (entityValue === "") {
this.Forget(entityName)
return
}
// If we don't already have entry in map for this item, create one
if (!this.map[entityName]) {
this.map[entityName] = {
entityId,
values: []
}
}
const displayText = builtinType && resolution ? ModelUtils.PrebuiltDisplayText(builtinType, resolution, entityValue) : entityValue
const newFilledEntityValue: MemoryValue = {
userText: entityValue,
displayText,
builtinType,
resolution,
enumValueId
}
const filledEntity = this.map[entityName]
// Check if entity buckets values
if (isBucket) {
// Add if not a duplicate
const containsDuplicateValue = filledEntity.values.some(memoryValue => memoryValue.userText === entityValue)
if (!containsDuplicateValue) {
filledEntity.values.push(newFilledEntityValue)
}
} else {
filledEntity.values = [newFilledEntityValue]
}
}
/** Return FilledEntity array for items I've remembered */
public FilledEntities(): FilledEntity[] {
return Object.values(this.map)
}
public static Split(action: string): string[] {
return action.split(/[\s,:.?!\[\]]+/)
}
}