🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Building natural language assistants with Laravel and Wit.ai: Part 1 - Setting up

  • Neo Ighodaro

September 3rd, 2019
You will need Laravel CLI, NPM and SQLite installed on your machine.

In this tutorial, we will be creating a chat bot that uses the wit.ai engine to parse the text of your customers and respond with the appropriate response.

The bot will be able to make arbitrary bookings on our behalf without any manual support from the customer agent. We will be using Pusher Chatkit as the chat provider for this application.

Virtual assistants like Siri and Cortana make it easy to dispatch voice commands and get tasks done using natural language. You can also create a text based chat bot that can use natural language to handle tasks.

It will look like this:

Prerequisites

To get started, you need the following:

  • A Pusher Chatkit application. Create one here.
  • A wit.ai application. Create one here.
  • Knowledge of PHP and the Laravel framework.
  • The Laravel CLI installed locally on your machine.
  • Basic knowledge of the Vue framework.
  • Latest NPM installed on your machine. Install here.
  • SQLite installed locally on your machine. Install here. (You can also use MySQL if you wish).

If you have all the requirements then let’s get started.

Setting up the applications

Before we start doing anything, let's set up all our dependencies so the application is easier to build.

Pusher Chatkit If you have not already, create a new Pusher account and log into the Chatkit dashboard. There you should create a new Chatkit application and store the credentials somewhere as we will use them later on.

When you are done creating the Chatkit application, create a new user called admin from the Console tab on the dashboard as seen below:

Next, make sure this user has all the Permissions ticked in the Roles tab of the console as seen below:

Wit.ai Create a new wit.ai account and when you are logged into the dashboard, create a new application by clicking + button on the top of the navigation bar.

Give your application a name and a description. You can download and import the training ZIP file in the repository. This will make the load of training the bot so much easier. However, if you want you can train it yourself later using the wit.ai dashboard and the docs. For the sake of this tutorial though, I suggest you import the ZIP file.

When you are done creating the bot, you can go to the settings page of the application and copy the Server Access Token. We will need this later.

Laravel application Now that we have set up the other apps, let’s set up the Laravel application. Open your terminal and run the following command to create a new Laravel application:

    $ laravel new alfred

You can replace “alfred” with the name of your application.

Now open the application in your favorite IDE. When the application is open, edit the .env file and change the database configuration as seen below:

    DB_CONNECTION=sqlite
    DB_DATABASE=path/to/database.sqlite

Next, create a new SQLite database file called database.sqlite in the database directory of your project.

If you are using MySQL then just adjust the DB_* connection settings according to your database information.

When you are done connecting the database, cd on your terminal to the project directory and run the following command to migrate the database:

    $ php artisan migrate

When the migration is completed, we have to do one last thing which we will need later on. This is creating an authentication layer. To do this, run the command below:

    $ php artisan make:auth

When the authentication command has been run successfully, start a PHP server using the in-built artisan command below:

    $ php artisan serve

Now your application will be available on http://localhost:8000. Next, click on the Register button on the top right of the application and create a new account.

One other thing we need to do is find a package that makes Wit and Laravel work together. The closest to being up to date I found was this one. However, we will need to override some things to make it work properly.

Making Chatkit and Laravel work To make Chatkit work with Laravel we have to take a few steps. First, open the .env file and paste the following at the bottom of the file:

    # File: .env
    # [...]

    CHATKIT_INSTANCE_LOCATOR=INSTANCE_LOCATOR_HERE
    CHATKIT_SECRET_KEY=SECRET_KEY_HERE
    MIX_CHATKIT_INSTANCE_LOCATOR="${CHATKIT_INSTANCE_LOCATOR}"

Replace both placeholders with the actual keys from your Chatkit dashboard. Note: Don’t change the value of the MIX_CHATKIT_INSTANCE_LOCATOR.

To enable our application can use the environment credentials we added earlier, open the config/services.php file and in there add the snippet below to the array of third-party services:

    // File: config/services.php
    'chatkit' => [
      'secret' => env('CHATKIT_SECRET_KEY'),
      'locator' => env('CHATKIT_INSTANCE_LOCATOR'),
    ],

Next we will need to do is install the Chatkit PHP SDK. Run this command in the root directory of your project to install the Chatkit package:

    $ composer require pusher/pusher-chatkit-server 

Next, open app/providers/AppServiceProvider.php and add the following code inside the register method:

    // File: app/providers/AppServiceProvider.php
    // [...]

    $this->app->bind(\Chatkit\Chatkit::class, function() {
        return new \Chatkit\Chatkit([
            'key' => config('services.chatkit.secret'),
            'instance_locator' => config('services.chatkit.locator'),
        ]);
    });

    // [...]

The above snippet will bind the Chatkit service within the register method into Laravel’s IoC container. We can now resolve it from anywhere within our app and it will return an instance of the configured Chatkit class.

Making Wit.ai and Laravel work To install the package, run the following command in the root of the application:

    $ composer require jeylabs/wit

When the package has been installed, we can start adding customizations. First, create a new service provider class called WitServiceProvider in the app/Providers directory and paste the following code into it:

    <?php
    // File: ./app/Providers/WitServiceProvider.php

    namespace App\Providers;

    use App\Wit;
    use Illuminate\Contracts\Container\Container;
    use Jeylabs\Wit\Laravel\WitServiceProvider as BaseServiceProvider;

    class WitServiceProvider extends BaseServiceProvider
    {
        protected function registerBindings(Container $app)
        {
            $app->singleton('wit', function ($app) {
                return new Wit(
                    $app['config']->get('wit.access_token', null),
                    $app['config']->get('wit.async_requests', false)
                );
            });

            $app->alias('wit', Wit::class);
        }
    }

In the code above, we are extending the service provider that comes with the package. This is so we can override the main class of the package. This allows us extend the package without hassle.

Next, register the service provider in the config/app.php file by adding the following to the list of service providers:

    // File: config/app.php
    // [...]

    'providers' => [
        // [...]

        App\Providers\WitServiceProvider::class,
    ],

    // [...]

Next, create a new Wit class in the app directory and paste the following code into it:

    <?php
    // File: ./app/Wit.php

    namespace App;

    use Jeylabs\Wit\Wit as BaseWit;

    class Wit extends BaseWit
    {
        const WIT_API_VERSION = '20190715';

        protected function makeRequest($method, $uri, $query = [], $data = [])
        {
            $query = array_merge($query, ['v' => static::WIT_API_VERSION]);

            return parent::makeRequest($method, $uri, $query, $data);
        }
    }

In the class above, we are extending the base Wit class that comes with the package. The only thing we would like to extend is the WIT_API_VERSION and the makeRequest method. You can also decide to add some new methods along the way if you want.

Next, open your .env file and paste the following code at the bottom:

    # File: .env
    # [...]

    WIT_ACCESS_TOKEN=SERVER_ACCESS_TOKEN

Replace the SERVER_ACCESS_TOKEN with the access token of your Wit.ai application.

At this point, your Laravel application is ready to interact with the Wit.ai application.

Building the admin chat backend

We will be building the backend for the application now. Before we get started, let’s install the npm packages. cd to the root of your Laravel application and in there run the following command:

     $ npm install

This will install all the npm packages into the application. Next, open the app.scss file and paste the following at the bottom of the file:

    // File: resources/sass/app.scss
    // [...]


    body {
        padding-top: 3.5rem;
    }

    h1 {
        padding-bottom: 9px;
        margin-bottom: 20px;
        border-bottom: 1px solid #eee;
    }

    .sidebar {
        position: fixed;
        top: 51px;
        bottom: 0;
        left: 0;
        z-index: 1000;
        padding: 20px 0;
        overflow-x: hidden;
        overflow-y: auto;
        border-right: 1px solid #eee;
        .nav {
            margin-bottom: 20px;
        }
        .nav-item {
            width: 100%;
        }
        .nav-item + .nav-item {
            margin-left: 0;
        }
        .nav-link {
            border-radius: 0;
        }
    }

    .placeholders {
        padding-bottom: 3rem;
        img {
            padding-top: 1.5rem;
            padding-bottom: 1.5rem;
        }
    }

    tr .sender {
        font-size: 12px;
        font-weight: 600;
        span {
            color: #676767;
        }
    }

    .response {
        display: none;
    }

Next, replace the code in the app.blade.php file with this:

    <!-- File: resources/views/layouts/app.blade.php -->
    <!DOCTYPE html>
    <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>{{ config('app.name', 'Laravel') }}</title>
        <link rel="dns-prefetch" href="//fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
        <script src="{{ asset('js/app.js') }}" defer></script>
        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    </head>
    <body>
        <div id="app">
            <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm fixed-top">
                <div class="container">
                    <a class="navbar-brand" href="{{ url('/') }}">
                        {{ config('app.name', 'Laravel') }}
                    </a>
                    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent">
                        <span class="navbar-toggler-icon"></span>
                    </button>

                    <div class="collapse navbar-collapse" id="navbarSupportedContent">
                        <ul class="navbar-nav mr-auto">
                        </ul>

                        <ul class="navbar-nav ml-auto">
                            @guest
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                                @if (Route::has('register'))
                                    <li class="nav-item">
                                        <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                    </li>
                                @endif
                            @else
                                <li class="nav-item dropdown">
                                    <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                        {{ Auth::user()->name }} <span class="caret"></span>
                                    </a>

                                    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                        <a class="dropdown-item" href="{{ route('logout') }}"
                                           onclick="event.preventDefault();
                                                         document.getElementById('logout-form').submit();">
                                            {{ __('Logout') }}
                                        </a>

                                        <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                            @csrf
                                        </form>
                                    </div>
                                </li>
                            @endguest
                        </ul>
                    </div>
                </div>
            </nav>

            <main class="py-4">
                @yield('content')
            </main>
        </div>
    </body>
    </html>

Next, open the home.blade.php file in the resources/views directory and replace the contents with the following:

    {{!-- File: resources/views/home.blade.php --}}
    @extends('layouts.app')

    @section('content')

    <div class="container-fluid">
        <div class="row" id="mainrow">
            <nav class="col-sm-3 col-md-2 d-none d-sm-block bg-light sidebar">
                <h5 class="container">Available Rooms</h5>
                <ul class="nav nav-pills flex-column" id="rooms">
                </ul>
            </nav>
            <main role="main" class="col-sm-9 ml-sm-auto col-md-10 pt-3" id="main">
                <h1>Chats</h1>
                <p>👈 Select a chat to load the messages</p>
                <p>&nbsp;</p>
                <div class="chat" style="margin-bottom:150px">
                    <h5 id="room-title"></h5>
                    <p>&nbsp;</p>
                    <div class="response">
                        <form id="replyMessage">
                            <div class="form-group">
                                <input type="text" placeholder="Enter Message" class="form-control" name="message" />
                            </div>
                        </form>
                    </div>
                    <div class="table-responsive">
                      <table class="table table-striped">
                        <tbody id="chat-msgs">
                        </tbody>
                    </table>
                </div>
            </main>
        </div>
    </div>

    @endsection

Next, run the following command to install the Chatkit JavaScript client:

    $ npm install @pusher/chatkit-client

When this is complete, create a new support.js file in the resources/js directory and paste the following content in it:

    // File: resources/js/support.js
    import { ChatManager, TokenProvider } from '@pusher/chatkit-client';

    window.PusherChatManager = new ChatManager({
        userId: 'admin',
        instanceLocator: process.env.MIX_CHATKIT_INSTANCE_LOCATOR,
        tokenProvider: new TokenProvider({ url: '/chatkit/authenticate' })
    });

    PusherChatManager.connect().then(currentUser => {
        let currentRoomId;

        // ----------------------------------------------------------------------
        // Add the list of rooms to the sidebar on the right of the dashboard
        // ----------------------------------------------------------------------

        for (let index = 0; index < currentUser.rooms.length; index++) {
            const room = currentUser.rooms[index];
            $('#rooms').append(
                `<li class="nav-item">
                    <a data-room-id="${room.id}" class="nav-link" href="#">
                        ${room.name}
                    </a>
                </li>`
            );
        }

        // ----------------------------------------------------------------------
        // On click of the chat room name, load the messages for the chatroom
        // ----------------------------------------------------------------------

        $('#rooms').on('click', 'li', ({ target }) => {
            const { roomId } = $(target).data();
            const roomName = $(target).text();

            const parseMessage = message => {
                let msg = '';
                for (let index = 0; index < message.parts.length; index++) {
                    const part = message.parts[index];
                    if (part.partType === 'inline') {
                        msg += part.payload.content;
                    }
                }

                $('#chat-msgs').prepend(
                    `<tr>
                    <td>
                        <div class="sender">
                            ${message.senderId} @ <span class="date">${message.createdAt}</span>
                        </div>
                        <div class="message">${msg}</div>
                    </td>
                </tr>`
                );
            };

            if (roomId) {
                $('#chat-msgs').html('') && $('.response').show();
                $('#room-title').text(`Room: ${roomName}`);

                currentRoomId = roomId;

                currentUser.subscribeToRoomMultipart({
                    messageLimit: 100,
                    roomId: `${roomId}`,
                    hooks: { onMessage: parseMessage }
                });
            }
        });

        // ----------------------------------------------------------------------
        // When a message is being responded to, fire the event below
        // ----------------------------------------------------------------------

        $('#replyMessage').on('submit', evt => {
            evt.preventDefault();

            currentUser
                .sendSimpleMessage({
                    roomId: `${currentRoomId}`,
                    text: $('#replyMessage input').val().trim()
                })
                .then(() => $('#replyMessage input').val(''));
        });
    });

In the script above, we load the Chatkit client, connect to the client using a TokenProvider (we will create the client/authenticate endpoint soon), then we define the callbacks to handle messaging on the admin frontend. The code is commented so you can see what is happening.

Before we create the authentication endpoint, let’s first include the JavaScript file into our build. Open the resources/js/app.js file and replace it’s entire content with the following:

    // File: resources/js/app.js
    require('./bootstrap');
    require('./support');

Now, let’s create the endpoint we need. In your terminal, run the following command to create a new Laravel Controller:

    $ php artisan make:controller ChatkitController

We will need this controller to add the authentication endpoint. Open the file in your text editor and replace the contents with the following code:

    <?php
    // File: app/Http/Controllers/ChatkitController.php

    namespace App\Http\Controllers;

    use Chatkit\Chatkit;
    use Illuminate\Http\Request;

    class ChatkitController extends Controller
    {
        public function __construct(Chatkit $chatkit)
        {
            $this->chatkit = $chatkit;
        }

        public function authenticate(Request $request)
        {
            $userId = $request->get('user_id');

            $response = $this->chatkit->authenticate(['user_id' => $userId]);

            return response()->json($response['body'], $response['status']);
        }
    }

In the authenticate method above, we just use the Chatkit PHP SDK to authenticate the user and generate a token which will then be used by the JavaScript client to make requests.

Next, open the routes/web.php file and register the route for the authentication endpoint by adding the following to the bottom of the file:

    // File: routes/web.php
    Route::post('/chatkit/authenticate', 'ChatkitController@authenticate');

To make sure Chatkit can send requests without being flagged by Laravel’s inbuilt CSRF security middleware, open the VerifyCsrfToken class and add this to the except property as seen below:

    <?php
    // File: app/Http/Middleware/VerifyCsrfToken.php
    // [...]

    class VerifyCsrfToken extends Middleware
    {
        // [...]

        protected $except = [
            '/chatkit/*'
        ];
    }

Next, let’s build the assets. Go to your terminal and run the following command:

    $ npm run dev

When the build is complete, you can run your application, log in and see the dashboard using the following command:

    $ php artisan serve

However, you will not be able to send any messages since you don’t have customers with open chats from the frontend.

We will build this in the next part.

Conclusion

In this part, we have been able to build the backend for our application and we have learned how we can connect Pusher Chatkit and Wit.ai to a Laravel application. The second part of this tutorial will consider how we can make them all work together.

The source code to the code in this tutorial is available on GitHub.

Clone the project repository
  • Chat
  • CSS
  • JavaScript
  • Laravel
  • PHP
  • Vue.js
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.