Skip to content

Commit

Permalink
Merge pull request #1734 from botpress/f_multi-entities-slots
Browse files Browse the repository at this point in the history
feat(nlu): multi entities slots
  • Loading branch information
EFF committed Apr 26, 2019
2 parents 657da19 + 2c9c374 commit fcb8d33
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 80 deletions.
2 changes: 1 addition & 1 deletion modules/nlu/package.json
Expand Up @@ -35,7 +35,7 @@
"ml-kmeans": "^4.2.1",
"ms": "^2.1.1",
"nanoid": "^2.0.1",
"react-select": "^1.2.1",
"react-select": "^2.4.3",
"react-splitter-layout": "^3.0.0",
"react-tag-input": "6.2.0",
"tmp": "^0.0.33",
Expand Down
5 changes: 4 additions & 1 deletion modules/nlu/src/backend/pipelines/slots/crf_extractor.ts
Expand Up @@ -162,7 +162,10 @@ export default class CRFExtractor implements SlotExtractor {
): sdk.NLU.Slot {
const slotDef = slotDefinitions.find(slotDef => slotDef.name === slotName)
const entity =
slotDef && entities.find(e => slotDef.entity === e.name && e.meta.start <= token.start && e.meta.end >= token.end)
slotDef &&
entities.find(
e => slotDef.entities.indexOf(e.name) !== -1 && e.meta.start <= token.start && e.meta.end >= token.end
)

const value = _.get(entity, 'data.value', token.value)

Expand Down
31 changes: 26 additions & 5 deletions modules/nlu/src/backend/storage.ts
Expand Up @@ -29,14 +29,21 @@ export default class Storage {

async saveIntent(intent: string, content: sdk.NLU.IntentDefinition) {
intent = sanitizeFilenameNoExt(intent)
const entities = await this.getAvailableEntities()
const availableEntities = await this.getAvailableEntities()

if (content.slots) {
await Promise.map(content.slots, async slot => {
if (!entities.find(e => e.name === slot.entity)) {
throw Error(`"${slot.entity}" is neither a system entity nor a custom entity`)
for (const slot of content.slots) {
// @deprecated > 11 gracefull migration
if (!slot.entities && slot.entity) {
slot.entities = [slot.entity]
}
})

for (const entity of slot.entities!) {
if (!availableEntities.find(e => e.name === entity)) {
throw Error(`"${entity}" is neither a system entity nor a custom entity`)
}
}
}
}

if (!content.contexts) {
Expand Down Expand Up @@ -110,8 +117,22 @@ export default class Storage {
}

// @deprecated remove in bp > 11
let hasChange = false
if (!properties.utterances) {
await this._legacyAppendIntentUtterances(obj, intent)
hasChange = true
}

// @deprecated > 11 graceful migration
for (const slot of obj.slots || []) {
if (!slot.entities && slot.entity) {
slot.entities = [slot.entity]
delete slot.entity
hasChange = true
}
}

if (hasChange) {
await this.saveIntent(intent, obj)
}

Expand Down
6 changes: 5 additions & 1 deletion modules/nlu/src/backend/validation.ts
Expand Up @@ -4,7 +4,11 @@ export const ID_REGEX = /^[a-zA-Z0-9]+[A-Z0-9_-]{2,}$/i

export const SlotsCreateSchema = Joi.object().keys({
name: Joi.string().required(),
entity: Joi.string().required(),
// @deprecated >11
entity: Joi.string().optional(),
entities: Joi.array()
.items(Joi.string())
.required(),
color: Joi.number().required(),
id: Joi.string().required()
})
Expand Down
6 changes: 3 additions & 3 deletions modules/nlu/src/views/full/entities/CreateEntityModal.jsx
Expand Up @@ -29,8 +29,8 @@ export default class CreateEntityModal extends React.Component {
this.validate()
}

handleTypeChange = selected => {
this.setState({ type: selected.value })
handleTypeChange = type => {
this.setState({ type })
this.validate()
}

Expand All @@ -40,7 +40,7 @@ export default class CreateEntityModal extends React.Component {
const entity = {
id: sanitizeFilenameNoExt(this.state.name),
name: this.state.name,
type: this.state.type,
type: this.state.type.value,
occurences: []
}
this.props.axios.post(`/mod/nlu/entities/`, entity).then(() => {
Expand Down
4 changes: 2 additions & 2 deletions modules/nlu/src/views/full/intents/editor.jsx
Expand Up @@ -7,7 +7,7 @@ import Editor from './draft/editor'

import style from './style.scss'
import Slots from './slots/Slots'
import { Creatable } from 'react-select'
import Creatable from 'react-select/lib/Creatable'
import classnames from 'classnames'
import { BotpressTooltip } from 'botpress/tooltip'

Expand Down Expand Up @@ -271,7 +271,7 @@ export default class IntentsEditor extends React.Component {
</div>
<Creatable
id="selectContext"
multi
isMulti
onChange={this.handleChangeContext}
value={this.state.selectedContextOptions}
options={
Expand Down
28 changes: 16 additions & 12 deletions modules/nlu/src/views/full/intents/slots/SlotModal.jsx
Expand Up @@ -5,13 +5,12 @@ import nanoid from 'nanoid'
import random from 'lodash/random'

import style from './style.scss'
import 'react-select/dist/react-select.css'

const N_COLORS = 12
const INITIAL_STATE = {
id: null,
name: '',
entity: null,
entities: [],
availableEntities: [],
editing: false,
color: false
Expand All @@ -35,8 +34,8 @@ export default class SlotModal extends React.Component {
this.setState({ name: event.target.value.replace(/[^A-Z0-9_-]/gi, '_') })
}

onEntityChanged = entity => {
this.setState({ entity: entity.value })
onEntitiesChanged = entities => {
this.setState({ entities })
}

componentDidMount() {
Expand All @@ -52,7 +51,12 @@ export default class SlotModal extends React.Component {

initializeFromProps = () => {
if (this.props.slot) {
this.setState({ ...this.props.slot, editing: true })
let slot = { ...this.props.slot }
slot.entities = slot.entities.map(e => ({
label: e,
value: e
}))
this.setState({ ...slot, editing: true })
} else this.resetState()
}

Expand All @@ -72,7 +76,7 @@ export default class SlotModal extends React.Component {
const slot = {
id: this.state.id || nanoid(),
name: this.state.name,
entity: this.state.entity,
entities: this.state.entities.map(e => e.value),
color: this.state.color || this.getNextAvailableColor()
}

Expand All @@ -86,10 +90,10 @@ export default class SlotModal extends React.Component {
}

render() {
const isValid = this.state.name && this.state.name.length && this.state.entity && this.state.entity.length
const isValid = this.state.name && this.state.name.length && this.state.entities && this.state.entities.length

return (
<Modal show={this.props.show} bsSize="small" onHide={this.props.onHide} animation={false} backdrop={'static'}>
<Modal show={this.props.show} onHide={this.props.onHide} animation={false} backdrop={'static'}>
<Modal.Header closeButton>
{this.state.editing && <Modal.Title>Edit slot</Modal.Title>}
{!this.state.editing && <Modal.Title>Create Slot for your intent</Modal.Title>}
Expand All @@ -104,13 +108,13 @@ export default class SlotModal extends React.Component {
placeholder="Type a name here"
onChange={this.onNameChange}
/>

<h4>Associated Entity</h4>
<h4>Associated Entities</h4>
<Select
isMulti
tabIndex="2"
name="entity-type"
value={this.state.entity}
onChange={this.onEntityChanged}
value={this.state.entities}
onChange={this.onEntitiesChanged}
options={this.state.availableEntities}
/>
</Modal.Body>
Expand Down

0 comments on commit fcb8d33

Please sign in to comment.