Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ botpress install api.ai
The API.AI module should now be available in your bot UI

## Features
### Multi-Language Agent

You can specify different agents and the languages they supports.

This module will try to query the agent that support the locale of the user.
If no agent contains the locale, it will look for the root language (eg: en instead of en-GB) otherwise the default language parameter is used.

For more information see https://api.ai/docs/multi-language

### Mode
This module has two modes: **Default** (amend incoming events) and **Fulfillment** (respond automatically).

### Default Mode
#### Default Mode

This mode will inject understanding metadata inside incoming messages through the API.AI middleware.

Expand All @@ -33,7 +42,7 @@ bp.hear({'nlp.source': 'agent'}, (event, next) => {
})
```

### Fulfillment Mode
#### Fulfillment Mode

This mode will check if there's an available response in the `fulfillment` property of the API.AI response and respond automatically. No code required.

Expand All @@ -54,7 +63,7 @@ Get an invite and join us now! 👉[https://slack.botpress.io](https://slack.bot
| ENV | Default | Description |
|---|---|---|
| BOTPRESS_HTTP_TIMEOUT | 5000 | The timeout to API.AI requests |
| APIAI_TOKEN | null | Override the API token |
| APIAI_TOKEN | null | Override the API token (depreciated after v2.1.3)|

## License

Expand Down
71 changes: 53 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,77 @@ import axios from 'axios'
let config = null
let service = null

const getClient = () => {
const getAgent = (lang) => {
if (config.agents.length == 0) {
// back compatibility
return {clientToken: config.accessToken}
}

for (const agent of config.agents) {
if (agent.langs.includes(lang)) {
return agent
}
}
}

const getAvailableLang = (lang) => {
if (!lang) {
return config.lang
}
lang = lang.replace('_', '-') // convert bp locale format to api.ai format

for (const agent of config.agents) {
if (agent.langs.includes(lang)) {
return lang
}
}

const l = lang.split('-')
if (l.length == 2) {
return getAvailableLang(l[0])
}

return config.lang
}

const getClient = (lang) => {
return axios.create({
baseURL: 'https://api.api.ai/v1',
timeout: process.env.BOTPRESS_HTTP_TIMEOUT || 5000,
headers: {'Authorization': 'Bearer ' + config.accessToken}
headers: {'Authorization': 'Bearer ' + getAgent(lang).clientToken}
})
}

const setService = () => {
service = (userId, text) => {
return getClient().post('/query?v=20170101', {
service = (userId, lang, text) => {
return getClient(lang).post('/query?v=20170101', {
query: text,
lang: config.lang,
lang: lang,
sessionId: userId
})
}
}

const contextAdd = userId => (name, lifespan = 1) => {
return getClient().post('/contexts?v=20170101', [
const contextAdd = (userId, lang) => (name, lifespan = 1) => {
return getClient(lang).post('/contexts?v=20170101', [
{ name, lifespan }
], { params: {sessionId: userId } })
}

const contextRemove = userId => name => {
return getClient().delete('/contexts/' + name, { params: { sessionId: userId } })
const contextRemove = (userId, lang) => name => {
return getClient(lang).delete('/contexts/' + name, { params: { sessionId: userId } })
}

const incomingMiddleware = (event, next) => {
const lang = getAvailableLang(event.user.locale)
let shortUserId = _.get(event, 'user.id') || ''
if (shortUserId.length > 36) {
shortUserId = crypto.createHash('md5').update(shortUserId).digest("hex")
}

if (["message", "postback", "text", "quick_reply"].includes(event.type)) {

service(shortUserId, event.payload || event.text)
service(shortUserId, lang, event.payload || event.text)
.then(({data}) => {
const {result} = data
if (config.mode === 'fulfillment'
Expand All @@ -67,8 +101,8 @@ const incomingMiddleware = (event, next) => {
} else {
event.nlp = Object.assign(result, {
context: {
add: contextAdd(shortUserId),
remove: contextRemove(shortUserId)
add: contextAdd(shortUserId, lang),
remove: contextRemove(shortUserId, lang)
}
})
next()
Expand All @@ -86,14 +120,14 @@ const incomingMiddleware = (event, next) => {

console.log(error.stack)

event.bp.logger.warn('botpress-api.ai', 'API Error. Could not process incoming text: ' + err);
event.bp.logger.warn('botpress-api.ai', 'API Error. Could not process incoming text: ' + err)
next()
})
} else {
event.nlp = {
context: {
add: contextAdd(shortUserId),
remove: contextRemove(shortUserId)
add: contextAdd(shortUserId, lang),
remove: contextRemove(shortUserId, lang)
}
}

Expand All @@ -104,7 +138,8 @@ const incomingMiddleware = (event, next) => {
module.exports = {

config: {
accessToken: { type: 'string', env: 'APIAI_TOKEN' },
accessToken: { type: 'string', env: 'APIAI_TOKEN' }, // back compatibility
agents: { type: 'any', required: true, default: [], validation: v => _.isArray(v) },
lang: { type: 'string', default: 'en' },
mode: { type: 'choice', validation: ['fulfillment', 'default'], default: 'default' }
},
Expand Down Expand Up @@ -133,8 +168,8 @@ module.exports = {
})

router.post('/config', async (req, res) => {
const { accessToken, lang, mode } = req.body
await configurator.saveAll({ accessToken, lang, mode })
const { agents, lang, mode } = req.body
await configurator.saveAll({ agents, lang, mode })
config = await configurator.loadAll()
setService()
res.sendStatus(200)
Expand Down
Loading