Skip to content

Commit

Permalink
feat(qna): Q&A module
Browse files Browse the repository at this point in the history
  • Loading branch information
emirotin committed Jun 1, 2018
1 parent 4e874fe commit f79e2e8
Show file tree
Hide file tree
Showing 22 changed files with 5,566 additions and 9 deletions.
9 changes: 6 additions & 3 deletions .eslintrc
Expand Up @@ -2,7 +2,7 @@
// http://eslint.org/docs/rules/
"parser": "babel-eslint",

"extends": ["prettier"],
"extends": ["prettier", "plugin:react/recommended"],

"env": {
"browser": true, // browser global variables.
Expand All @@ -26,7 +26,7 @@
"goog": true
},

"plugins": ["prettier", "jest"],
"plugins": ["prettier", "jest", "react"],

"rules": {
"no-extra-semi": 1, // disallow unnecessary semicolons
Expand All @@ -42,6 +42,7 @@

"no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block
"no-var": 2,
"no-unused-vars": 1,
"prefer-const": ["error", {
"destructuring": "all",
"ignoreReadBeforeAssign": false
Expand All @@ -50,6 +51,8 @@
"no-array-constructor": 2,
"no-new-object": 2,

"prettier/prettier": "error"
"prettier/prettier": "error",

"react/prop-types": 0
}
}
3 changes: 3 additions & 0 deletions packages/dev-bot/config/qna.json
@@ -0,0 +1,3 @@
{
"qnaDir": "./generated/qna"
}
@@ -0,0 +1 @@
{ "entities": [] }
@@ -0,0 +1,3 @@
who is zebra?
who is donkey?
who is monkey?
8 changes: 8 additions & 0 deletions packages/dev-bot/generated/qna/6d2c0js5y6_who_is_donkey.json
@@ -0,0 +1,8 @@
{
"id": "6d2c0js5y6_who_is_donkey",
"data": {
"questions": ["who is zebra?", "who is donkey?", "who is monkey?"],
"answer": "animal!",
"enabled": true
}
}
1 change: 1 addition & 0 deletions packages/dev-bot/package.json
Expand Up @@ -14,6 +14,7 @@
"@botpress/hitl": "^10.11.2",
"@botpress/nlu": "^10.11.2",
"@botpress/skill-choice": "^10.11.3",
"@botpress/qna": "^10.11.2",
"botpress": "^10.11.2",
"jsdoc-api": "^4.0.3",
"lodash": "^4.17.10",
Expand Down
4 changes: 2 additions & 2 deletions packages/dev-bot/yarn.lock
Expand Up @@ -165,8 +165,8 @@ minimist@0.0.8:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"

mkdirp2@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/mkdirp2/-/mkdirp2-1.0.3.tgz#cc8dd8265f1f06e2d8f5b10b6e52f4e050bed21b"
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp2/-/mkdirp2-1.0.4.tgz#56de1f8f5c93cf2199906362eba0f9f262ee4437"

mkdirp@~0.5.1:
version "0.5.1"
Expand Down
5 changes: 3 additions & 2 deletions packages/functionals/botpress-nlu/src/index.js
Expand Up @@ -157,7 +157,8 @@ module.exports = {

bp.nlu = {
processEvent,
provider
provider,
storage
}

bp.middlewares.register({
Expand All @@ -183,7 +184,7 @@ module.exports = {
})

router.post('/intents/:intent', async (req, res) => {
await storage.saveIntent(req.params.intent, req.body && req.body)
await storage.saveIntent(req.params.intent, req.body)
res.sendStatus(200)
})

Expand Down
4 changes: 2 additions & 2 deletions packages/functionals/botpress-nlu/src/storage.js
Expand Up @@ -24,8 +24,8 @@ export default class Storage {
mkdirp.sync(path.resolve(this.projectDir, this.intentsDir))
mkdirp.sync(path.resolve(this.projectDir, this.entitiesDir))

await this.ghost.addRootFolder(this.intentsDir, '**/*.*')
await this.ghost.addRootFolder(this.entitiesDir, '**/*.entity.json')
await this.ghost.addRootFolder(this.intentsDir, { filesGlob: '**/*.*' })
await this.ghost.addRootFolder(this.entitiesDir, { filesGlob: '**/*.entity.json' })
}

async saveIntent(intent, content) {
Expand Down
2 changes: 2 additions & 0 deletions packages/functionals/botpress-qna/.npmignore
@@ -0,0 +1,2 @@
src/
webpack.js
76 changes: 76 additions & 0 deletions packages/functionals/botpress-qna/LICENSE

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions packages/functionals/botpress-qna/README.md
@@ -0,0 +1,29 @@
# Botpress Q&A ⚡

Botpress Q&A is a Botpress module that adds the unified Q&A management interface to your bot admin panel.

It relies on the NLU module for recognizing the questions. It also requires the `builtins` module to be present.

# Installation

⚠️ **This module only works with the new [Botpress X](https://github.com/botpress/botpress).**

- Install the peer dependencies and the module iteslf `yarn add @botpress/builtins @botpress/nlu @botpress/qna`
- Configure [NLU](https://github.com/botpress/botpress/tree/master/packages/functionals/botpress-nlu#botpress-nlu-)

# Usage

Go to the bot admin panel and choose Q&A from the left hand side menu.

# Contributing

The best way to help right now is by helping with the exising issues here on GitHub and by reporting new issues!

# License

Botpress is dual-licensed under [AGPLv3](/licenses/LICENSE_AGPL3) and the [Botpress Proprietary License](/licenses/LICENSE_BOTPRESS).

By default, any bot created with Botpress is licensed under AGPLv3, but you may change to the Botpress License from within your bot's web interface in a few clicks.

For more information about how the dual-license works and why it works that way please see the <a href="https://botpress.io/faq">FAQS</a>.

3 changes: 3 additions & 0 deletions packages/functionals/botpress-qna/config.json
@@ -0,0 +1,3 @@
{
"qnaDir": "./qna"
}
69 changes: 69 additions & 0 deletions packages/functionals/botpress-qna/package.json
@@ -0,0 +1,69 @@
{
"name": "@botpress/qna",
"version": "10.11.1",
"description": "Botpress Q&A module",
"main": "bin/node.bundle.js",
"homepage": "https://github.com/botpress/modules",
"repository": {
"type": "git",
"url": "https://github.com/botpress/modules"
},
"botpress": {
"menuText": "Q&A",
"menuIcon": "question_answer",
"webBundle": "bin/web.bundle.js",
"moduleView": {
"stretched": true
}
},
"publishConfig": {
"access": "public"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "node webpack.js --watch",
"compile": "node webpack.js --compile"
},
"author": "Botpress, Inc.",
"license": "AGPL-3.0",
"peerDependencies": {
"botpress": ">= 10.0.0",
"@botpress/nlu": ">= 10.0.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-latest": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
"brace": "^0.11.0",
"classnames": "^2.2.5",
"copy-webpack-plugin": "^4.4.1",
"core-js": "^2.4.1",
"css-loader": "^0.28.9",
"draft-js": "^0.10.4",
"draftjs-utils": "^0.9.1",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"node-sass": "^3.11.2",
"react-select": "^1.2.1",
"react-splitter-layout": "^3.0.0",
"sass-loader": "^6.0.6",
"style-loader": "^0.20.1",
"webpack": "^3.11.0",
"webpack-node-externals": "^1.6.0"
},
"dependencies": {
"axios": "^0.17.1",
"bluebird": "^3.5.1",
"bluebird-retry": "^0.11.0",
"lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"ms": "^2.1.1",
"nanoid": "^1.0.1"
}
}
73 changes: 73 additions & 0 deletions packages/functionals/botpress-qna/src/index.js
@@ -0,0 +1,73 @@
import Storage from './storage'
import { processEvent } from './middleware'

let storage
let logger

module.exports = {
config: {
qnaDir: { type: 'string', required: true, default: './qna', env: 'QNA_DIR' }
},
async init(bp, configurator) {
const config = await configurator.loadAll()
storage = new Storage({ bp, config })
await storage.initializeGhost()

logger = bp.logger

bp.middlewares.register({
name: 'qna.incoming',
module: 'botpress-qna',
type: 'incoming',
handler: async (event, next) => {
if (!await processEvent(event, { storage, logger })) {
next()
}
},
order: 11, // must be after the NLU middleware and before the dialog middleware
description: 'Listen for predefined questions and send canned responses.'
})
},
ready(bp) {
const router = bp.getRouter('botpress-qna')

router.get('/', async (req, res) => {
try {
res.send(await storage.getQuestions())
} catch (e) {
logger.error('QnA Error', e, e.stack)
res.status(500).send(e.message || 'Error')
}
})

router.post('/', async (req, res) => {
try {
const id = await storage.saveQuestion(null, req.body)
res.send(id)
} catch (e) {
logger.error('QnA Error', e, e.stack)
res.status(500).send(e.message || 'Error')
}
})

router.put('/:question', async (req, res) => {
try {
await storage.saveQuestion(req.params.question, req.body)
res.end()
} catch (e) {
logger.error('QnA Error', e, e.stack)
res.status(500).send(e.message || 'Error')
}
})

router.delete('/:question', async (req, res) => {
try {
await storage.deleteQuestion(req.params.question)
res.end()
} catch (e) {
logger.error('QnA Error', e, e.stack)
res.status(500).send(e.message || 'Error')
}
})
}
}
22 changes: 22 additions & 0 deletions packages/functionals/botpress-qna/src/middleware.js
@@ -0,0 +1,22 @@
import { NLU_PREFIX } from './storage'

export const processEvent = async (event, { storage, logger }) => {
if (!event.nlu.intent || !event.nlu.intent.startsWith(NLU_PREFIX)) {
return false
}

logger.debug('QnA: matched NLU intent', event.nlu.intent.name)
const id = event.nlu.intent.name.substring(NLU_PREFIX.length)
const { data } = await storage.getQuestion(id)
// actually this shouldn't be the case as we delete intents
// for disabled questions
if (!data.enabled) {
logger.debug('QnA: question disabled, skipping')
return false
}

logger.debug('QnA: replying to recognized question', id)
event.reply('#builtin_text', { text: data.answer })
// return `true` to prevent further middlewares from capturing the message
return true
}

0 comments on commit f79e2e8

Please sign in to comment.