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

  1. A controller
  2. A content type
  3. 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.

Botpress custom component
Botpress custom component
Here I am using getsampledata action to get hard coded chart data & options, but you can query your REST endpoint.

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

Botpress custom component

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


Simon

I am a Fullstack developer and Consultant with an experience of 9+ years in the industry. I mainly work on Java, React, Javascript, NodeJs, Elasticsearch and Botpress.

22 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…….

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

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`.

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.

ปั้มไลค์ · 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 etc

      Harald · 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)

Leave a Reply

Your email address will not be published. Required fields are marked *