Botpress is an excellent bot framework, which is on-prem, and open-source, with plenty of options for customizations. This already has some built-in components like text, card, carousel, image, and dropdown, but what if we want something entirely different. Let us create a custom component in Botpress?
In this tutorial, we are going to create a chart component to visualize our data. We are going to use primefaces to build this component which internally uses Chart.js.
Note: Botpress uses Reactjs to create components. Check out What, Why and First React Code tutorial for a quick 10 minutes read on getting started with Reactjs.
Before we can jump in to create our custom component, one needs to create a custom module. Please follow along this guide to create your custom module, then come back and resume.
Ok assuming you have created a custom module named infa-module, you should get the details of your module in the modules\infa-module\src\backend\index.ts file.
In order to use primereact first add it as your dependency
yarn add primereact
A custom component needs 3 items
- A controller
- A content type
- Registration of this component in your bot.
Controller
Create your component controller inside your module modules\infa-module\src\views\lite\components\InfaChart.jsx
Note: For custom css classes please refer here.
import React from 'react' import { Chart } from 'primereact/chart'; const noAnswer = "Sorry no answer found." const NoAnswerMessage = () => { return ( <div className="infaTextMain"> <p className="infaTextMessage">{noAnswer}</p> <small className="infaTextTimestamp"></small> </div> ) } export class InfaChart extends React.Component { data = this.props.response.data.user.response.data; options = this.props.response.data.user.response.options; type = this.props.response.data.user.response.type; componentDidMount() { this.options = this.props.response.data.user.response.options; } getTimestamp = () => { let date = new Date(); let options = { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }; return date.toLocaleTimeString("en-us", options); } render() { if (this.data.datasets[0].data.length > 0) { return ( <Chart type={this.type} data={this.data} options={this.options} /> ) } else { return <NoAnswerMessage />; } } }
add this component controller in your lite index file modules\infa-module\src\views\lite\index.jsx
export { InfaChart } from './components/InfaChart'
Content Type
Create a content type in modules\infa-module\src\content-types\infa-chart.js
const base = require('./_base'); function render(data) { const events = []; return [...events, { type: 'custom', module: 'infa-module', component: 'InfaChart', text: data.text, response: { data } }] } function renderMessenger(data) { return [{ type: 'typing', value: data.typing }, { text: data.text }]; } function renderElement(data, channel) { if (channel === 'web' || channel === 'api') { return render(data); } else if (channel === 'messenger') { return renderMessenger(data); } return []; } module.exports = { id: 'infa_chart', group: 'Custom Component', title: 'Infa Chart', jsonSchema: { description: 'Chart type is defined using the type property. Currently there are 2 options available; "doughtnut", "bar".', type: 'object', required: ['text'], properties: { text: { type: 'string', title: 'Message' }, variations: { type: 'array', title: 'Alternates (optional)', items: { type: 'string', default: '' } }, ...base.typingIndicators } }, uiSchema: { text: { 'ui:field': 'i18n_field', $subtype: 'textarea' }, variations: { 'ui:options': { orderable: false } } }, computePreviewText: formData => formData.text && 'Infa Chart: ' + formData.text, renderElement: renderElement }
Registration
Enable this component by adding the component id inside #contentTypes of out\bp\data\bots\<BOT_ID>\bot.config.json file.
That’s it, now build and start Botpress server.
yarn && yarn build && yarn start
If everything is fine you should see your new shiny custom component under the Pick Content menu.
modules\infa-module\src\actions\getsampledata.js
//const axios = require('axios'); /** * @title Get sample data for chart components * @category Utility * @author: Abhishek Raj Simon * @param {any} data - Query value you want to send in payload */ const defaultColor = '#42A5F5'; const color = ['#EC407A', '#AB47BC', '#42A5F5', '#7E57C2', '#66BB6A', '#FFCA28', '#26A69A' ]; const abbreviatedNumber = (value) => { let newValue = value; console.log(value); if (value >= 1000) { let suffixes = ["", "k", "m", "b", "t"]; let suffixNum = Math.floor(("" + value).length / 3); let shortValue = ''; for (let precision = 2; precision >= 1; precision--) { shortValue = parseFloat((suffixNum !== 0 ? (value / Math.pow(1000, suffixNum)) : value).toPrecision(precision)); let dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g, ''); if (dotLessShortValue.length <= 2) { break; } } if (shortValue % 1 !== 0) shortValue = shortValue.toFixed(1); newValue = shortValue + suffixes[suffixNum]; } return newValue; } const noAnswer = "Sorry no answer found." const barOptions = { plugins: { labels: { render: 'value', fontSize: 10, fontStyle: 'bold', fontColor: '#000', fontFamily: '"Lucida Console", Monaco, monospace' } }, legend: { display: false, position: 'bottom' }, scales: { xAxes: [{ type: 'time', display: true, time: { unit: 'month' } }], yAxes: [{ ticks: { callback: function (label, index, labels) { console.log(label); return this.abbreviatedNumber(label); } }, scaleLabel: { display: false, labelString: '1k = 1000' } }] } }; const barData = { datasets: [{ label: "Rows Processed", backgroundColor: defaultColor, data: [{ x: 1546300800000, y: 216915890 }, { x: 1548979200000, y: 1306458666 }, { x: 1551398400000, y: 1780817685 }, { x: 1554076800000, y: 1677704491 }, { x: 1556668800000, y: 2027622415 }, { x: 1559347200000, y: 1954768048 }, { x: 1561939200000, y: 683285217 } ] }] }; const getsampledata = async () => { console.log(args.data); const doughnut = 'doughnut'; const bar = 'bar' try { /* Here you can extend your component to have multiple chart types if (args.data === doughnut) { user.response = { data: doughnutData, options: doughnutOptions, type: doughnut }; } else */if (args.data === bar) { user.response = { data: barData, options: barOptions, type: bar }; } } catch (e) { console.log(e); } }; return getsampledata();
Result
That’s all is required to create a custom component in Botpress. Let me know in the comments section if you face any issues creating your custom component in Botpress.
Note: Using Botpress version 12.5.0
25 Comments
srini · August 23, 2019 at 3:53 pm
hi im new to bot press and tried to use ur help and created chart. but i dont get anything displayed. i even tried with simple text instead of chart inside the div tags. Will you be able to help?
Abhishek Simon · August 24, 2019 at 12:18 am
Sure will try to help you. Just so that we are on the same page, you successfully created a custom module and then created a custom component using the div tags. Is this correct? Do you see any exceptions in browser console or the botpress server?
srini · August 24, 2019 at 12:03 pm
thanks for the reply…. yes i did created the custom module and the component. In the chat window i get like a oval shape structure but not the chart. I do get errors in the browser. can you ping me in my email address or can you please share u email address i can share the screenshots.
Thanks a ton…….
Abhishek Simon · August 24, 2019 at 6:31 pm
Drop me a mail at abhisheksimion@gmail.com
srini · August 24, 2019 at 1:02 pm
this is the error i get in browser http://localhost:3000/assets/modules/cs_chart/web/lite.bundle.js net::ERR_ABORTED 404 (Not Found) could be the module not registered properly
Abhishek Simon · September 17, 2019 at 5:22 pm
Can you share your module without the node_modules folder to abhisheksimion@gmail.com. Also please check my github repository
https://github.com/abhisheksimion/botpress_infa_module
viet · August 26, 2019 at 2:30 pm
what is component id here ?
Abhishek Simon · August 26, 2019 at 4:46 pm
@viet: Component id is defined in the content type file `modules\infa-module\src\content-types\infa-chart.js` under `module.exports.id` in above example it is `infa_chart`.
Nguyễn Tiến Việt · August 26, 2019 at 5:24 pm
i had implemented with your code but everythings dont work for me, could you share your github folder ?
Abhishek Simon · September 17, 2019 at 5:20 pm
Github repository: https://github.com/abhisheksimion/botpress_infa_module
Priyanka Paul · November 6, 2019 at 6:33 pm
hi I want to make one dynamic dropdown that means the number of items in the dropdown will depend on the number of data fetched from the database. For that do I need to make a custom component ? and will this work on the current version as well?
Abhishek Simon · November 17, 2019 at 8:22 pm
Yes you can create a custom component for your own dynamic dropdown and it will work with the all 11.x+ versions, given you are following this guide.
priyanka · November 15, 2019 at 8:40 pm
Hi I am trying build one custom dynamic dropdown component. For that I am fetching data using an action and the data is stored in my session.lastMessages as an array of object where each coloumn comes as an attribute of the object.
I built a custom module and I wrote my jsx as
dynamicDropdown component:
import React from ‘react’
import Select from ‘react-select’
import Creatable from ‘react-select/lib/Creatable’
export class dynamicDropdownQuestions extends React.Component {
state = {
options: []
}
componentDidMount() {
if (this.props.session.lastMessages){
let Questions = [];
Questions = this.props.session.lastMessages.map(x => {
return {
value: x.Questions,
label: x.Questions
}
})
this.setState({ options: Questions })
}
}
handleChange = selectedOption => {
this.setState({ selectedOption }, () => {
if (!this.props.buttonText) {
this.sendChoice()
}
})
}
sendChoice = () => {
const { selectedOption } = this.state
if (!selectedOption) {
return
}
let { label, value } = this.state.selectedOption
if (selectedOption.length) {
label = selectedOption.map(x => x.label).join(‘,’)
value = selectedOption.map(x => x.value || x.label).join(‘,’)
}
this.props.onSendData && this.props.onSendData({ type: ‘quick_reply’, text: label, payload: value || label })
}
renderSelect(inKeyboard) {
return (
{this.props.allowCreation ? (
) : (
)}
{this.props.buttonText && (
{this.props.buttonText}
)}
)
}
render() {
const shouldDisplay = this.props.isLastGroup && this.props.isLastOfGroup
if (this.props.displayInKeyboard) {
const Keyboard = this.props.keyboard
return (
{this.props.message}
)
}
return (
{this.props.message}
{shouldDisplay && this.renderSelect()}
)
}
}
for the content type i have just copy pasted the already existing content type of the dropdown.If i try to change any thing my file is not building. and if I keep the same it still looks like an empty dropdown and the submit button does not work. Can anyone please help me in this.
Abhishek Simon · November 17, 2019 at 8:24 pm
Can you share your custom module as a zip to my email id abhisheksimion@gmail.com, I will try to look into it.
ปั้มไลค์ · May 31, 2020 at 11:18 pm
Like!! Really appreciate you sharing this blog post.Really thank you! Keep writing.
Simon · May 31, 2020 at 11:31 pm
You are welcome.
Harald · June 8, 2020 at 5:05 pm
i have clone https://github.com/abhisheksimion/botpress_infa_module
and do
Quick Start
Copy the contents to folder modules\infa-module
Open a terminal in the folder modules/infa-module and type yarn && yarn build
and i get this error?
harald@Entwicklung:~/botpress/modules/botpress_infa_module$ yarn build
yarn run v1.22.4
$ ./node_modules/.bin/module-builder build
[botpress_infa_module] Generated backend (581 ms)
[botpress_infa_module] Child
3 modules
Child
11 modules
ERROR in /home/harald/botpress/node_modules/primereact/components/chart/Chart.js
Module not found: Error: Can’t resolve ‘chart.js’ in ‘/home/harald/botpress/node_modules/primereact/components/chart’
ERROR in ./src/views/lite/components/InfaChart.jsx
Module not found: Error: Can’t resolve ‘chartjs-plugin-labels’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
ERROR in /home/harald/botpress/node_modules/primereact/components/chart/Chart.js
Module not found: Error: Can’t resolve ‘classnames’ in ‘/home/harald/botpress/node_modules/primereact/components/chart’
ERROR in /home/harald/botpress/node_modules/primereact/components/chart/Chart.js
Module not found: Error: Can’t resolve ‘prop-types’ in ‘/home/harald/botpress/node_modules/primereact/components/chart’
ERROR in ./src/views/lite/components/InfaLinkPreview.jsx
Module not found: Error: Can’t resolve ‘react-horizontal-scrolling-menu’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
ERROR in ./src/views/lite/components/InfaDropdownMenu.jsx
Module not found: Error: Can’t resolve ‘react-select’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
ERROR in ./src/views/lite/components/InfaLinkPreview.jsx
Module not found: Error: Can’t resolve ‘truncate’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Simon · June 8, 2020 at 5:39 pm
You need to add the missing components
eg:
yarn add primereact
,yarn add truncate
etcHarald · June 8, 2020 at 5:54 pm
primereact I had already installed ,truncate I have installed. This does not fix the error. Where can I find an overview of the other modules you mean by etc.?
Simon · June 8, 2020 at 6:58 pm
Try the following and check this for more details on primereact
yarn add primereact
yarn add primeicons
yarn add chart.js
yarn add classnames
yarn add prop-types
yarn add chartjs-plugin-labels
yarn add react-horizontal-scrolling-menu
yarn add react-select
yarn add truncate
Harald · June 8, 2020 at 7:55 pm
thanks for your patience. Now that I have installed it, the editor also shows the corresponding files. But in the module properties there is only ” Some interface” and in the Pick Content there are no new entries.
Harald · June 8, 2020 at 8:03 pm
In an new empty bot i see the entrys in content pick.
In the logs is a error:
16:29:57.699 HTTP [customactiondemo] [Error, Content type “infa_dropdownmenu” is not a valid registered content type ID]
STACK TRACE
Error: Content type “infa_dropdownmenu” is not a valid registered content type ID
at CMSService.getContentType (/home/harald/botpress/out/bp/core/services/cms.js:328:19)
at Promise.map.x (/home/harald/botpress/out/bp/core/services/cms.js:320:60)
at tryCatcher (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/util.js:16:23)
at MappingPromiseArray._promiseFulfilled (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/map.js:61:38)
at MappingPromiseArray.PromiseArray._iterate (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/promise_array.js:114:31)
at MappingPromiseArray.init (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/promise_array.js:78:10)
at MappingPromiseArray._asyncInit (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/map.js:30:10)
at _drainQueueStep (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:142:12)
at _drainQueue (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:131:9)
at Async._drainQueues (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:147:5)
at Immediate.Async.drainQueues [as _onImmediate] (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:694:18)
at tryOnImmediate (timers.js:665:5)
at processImmediate (timers.js:647:5)
at process.topLevelDomainCallback (domain.js:121:23)
Beto Fernandes · May 1, 2021 at 12:17 am
Hi, why I see this code in Github?
Simon · May 14, 2021 at 12:03 pm
I did not get your question. Can you please elaborate on your issue?
Deepak K T · November 22, 2022 at 7:47 pm
Hi Simon, I am new to Botpress and I want to add some custom analytics. Hence can you please write one blog on how to add custom analytics in Botpress analytics that would me so much helpful.