Introduction

In this tutorial, you will be guided to build powerful chatbots on Power Virtual Agents(PVA) and then host it on SharePoint using SharePont Framework. PVA is a new service from Microsoft that just came out of public preview in mid 2020. It allows users to easily build powerful and complex chatbots using a no-code user interface. If used together with SharePoint, it empowers companies to easily build chatbots for their company intranets that can answer employees’ frequently asked questions, find people or documents and more. This post is a step by step guide on how you can build a chatbot using PVA and host it on SharePoint using SPFx extensions.

PVA Chatbot in SharePoint in action

Prerequisites

1. Creating a bot on PVA & obtain bot ID

Head over to the PVA Portal and follow the on-screen step-by-step instructions to create your PVA bot. In here, you will be able to choose what language you want your bot to understand. Currently, you can only have 1 language per bot. Create new bot popup on PVA From the left panel, select "Manage > Channels" and then select "Custom Website". Copy the Bot ID and paste it somewhere, we will need it later. Bot ID

2. Create SPFx extension

We will need to create a SPFx extension in order to host our PVA bot on SharePoint. SPFx extension allows us to deploy our bot to all pages in a single site. If you just want to create a bot in a single page, you can use SPFx web parts instead.

To create a SPFx extension, open your command line and in the folder that you want to create your SPFx extension, run the command below and key in the following values. Feel free to change the name and description to something to your liking.

yo @microsoft/sharepoint

What is your solution name? pva-chatbot
Which baseline packages do you want to target for your component(s)? SharePoint Online only (latest)
Where do you want to place the files? Use the current folder
Do you want to allow the tenant admin the choice of being able to deploy the solution to all sites immediately without running any feature deployment or adding apps in sites? No
Will the components in the solution require permissions to access web APIs that are unique and not shared with other components in the tenant? No
Which type of client-side component to create? Extension
Which type of client-side extension to create? Application Customizer
What is your Application Customizer name? PVAChatbot
What is your Application Customizer description? PVAChatbot description

Make sure you are on Node.js v10.x, see the SPFx Extension Getting Started Guide if you are stuck.

3. Modify SPFx Extension to host PVA Bot

Install Bot Framework Web Chat using the command below:

npm install botframework-webchat

Create a "Chatbot.tsx" file in "src/extensions/<project_name>" and paste the following code into the file. Make sure you change the BOT_ID variable on line 25

import * as React from "react";
import { useBoolean, useId } from '@uifabric/react-hooks';
import * as ReactWebChat from 'botframework-webchat';
import { Dialog, DialogType } from 'office-ui-fabric-react/lib/Dialog';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Spinner } from 'office-ui-fabric-react/lib/Spinner';

export interface IChatbotProps { }

const dialogContentProps = {
    type: DialogType.normal,
    title: 'PVA Chatbot',
    closeButtonAriaLabel: 'Close'
};
export const PVAChatbotDialog: React.FunctionComponent = () => {
    const [hideDialog, { toggle: toggleHideDialog }] = useBoolean(true);
    const labelId: string = useId('dialogLabel');
    const subTextId: string = useId('subTextLabel');
    const modalProps = React.useMemo(
        () => ({
            isBlocking: false,
        }),
        [labelId, subTextId],
    );
    const BOT_ID = "YOUR_BOT_ID";
    const theURL = "https://powerva.microsoft.com/api/botmanagement/v1/directline/directlinetoken?botId=" + BOT_ID;
    const store = ReactWebChat.createStore(
        {},
        ({ dispatch }) => next => action => {
            return next(action);
        }
    );
    fetch(theURL)
        .then(response => response.json())
        .then(conversationInfo => {
            document.getElementById("loading-spinner").style.display = 'none';
            document.getElementById("webchat").style.minHeight = '50vh';
            ReactWebChat.renderWebChat(
                {
                    directLine: ReactWebChat.createDirectLine({
                        token: conversationInfo.token,
                    }),
                    store: store,
                },
                document.getElementById('webchat')
            );
        })
        .catch(err => console.error("An error occurred: " + err));

    return (
        <>
            <DefaultButton secondaryText="Opens the Chatbot Dialog" onClick={toggleHideDialog} text="Open Chatbot Dialog" />
            <Dialog styles={{
                main: { selectors: { ['@media (min-width: 480px)']: { width: 450, minWidth: 450, maxWidth: '1000px' } } }
            }} hidden={hideDialog} onDismiss={toggleHideDialog} dialogContentProps={dialogContentProps} modalProps={modalProps}>
                <div id="chatContainer" style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
                    <div id="webchat" role="main" style={{ width: "100%", height: "0rem" }}></div>
                    <Spinner id="loading-spinner" label="Loading..." style={{ paddingTop: "1rem", paddingBottom: "1rem" }} />
                </div>
            </Dialog>
        </>
    );
};

export default class Chatbot extends React.Component<IChatbotProps> {
    constructor(props: IChatbotProps) {
        super(props);
    }
    public render(): JSX.Element {
        return (
            <div style={{ display: "flex", flexDirection: "column", alignItems: "center", paddingBottom: "1rem" }}>
                <PVAChatbotDialog />
            </div>
        );
    }
}  

Head over to "<project_name>ApplicationCustomizer.ts" file, and paste the code below. This will create a Bottom Placeholder across all your pages in the SharePoint site and within the placeholder, it will render the Chatbot Component that we just created.

import { override } from '@microsoft/decorators';
import { Log } from '@microsoft/sp-core-library';
import * as React from "react";  
import * as ReactDOM from "react-dom";  
import {
  BaseApplicationCustomizer,
  PlaceholderContent,
  PlaceholderName
} from '@microsoft/sp-application-base';
import * as strings from 'PvaChatbotApplicationCustomizerStrings';
import Chatbot from "./Chatbot";  

const LOG_SOURCE: string = 'PvaChatbotApplicationCustomizer';

/**
 * If your command set uses the ClientSideComponentProperties JSON input,
 * it will be deserialized into the BaseExtension.properties object.
 * You can define an interface to describe it.
 */
export interface IPvaChatbotApplicationCustomizerProperties {
  Bottom: string;
}

/** A Custom Action which can be run during execution of a Client Side Application */
export default class PvaChatbotApplicationCustomizer
  extends BaseApplicationCustomizer<IPvaChatbotApplicationCustomizerProperties> {
  private _bottomPlaceholder: PlaceholderContent | undefined;
  @override
  public onInit(): Promise<void> {
    Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);
    // Wait for the placeholders to be created (or handle them being changed) and then
    // render.
    this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
    return Promise.resolve<void>();
  }

  private _renderPlaceHolders(): void {
    // Handling the bottom placeholder
    if (!this._bottomPlaceholder) {
      this._bottomPlaceholder = this.context.placeholderProvider.tryCreateContent(
        PlaceholderName.Bottom,
        { onDispose: this._onDispose }
      );

      // The extension should not assume that the expected placeholder is available.
      if (!this._bottomPlaceholder) {
        console.error("The expected placeholder (Bottom) was not found.");
        return;
      }
      const elem: React.ReactElement = React.createElement(Chatbot);
      ReactDOM.render(elem, this._bottomPlaceholder.domElement);
    }
  }
  private _onDispose(): void {
  }
}

4. Serving & testing locally on SharePoint

Head over to "config/serve.json" and change the pageUrl to your own SharePoint site url.

{
  "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
  "port": 4321,
  "https": true,
  "serveConfigurations": {
    "default": {
      "pageUrl": "https://<YOUR_SHAREPOINT_DOMAIN>.sharepoint.com/sites/<YOUR_SHAREPOINT_SITE>",
      "customActions": {
        "8541004f-b8f6-4863-a3fd-0721ec95f7cd": {
          "location": "ClientSideExtension.ApplicationCustomizer",
          "properties": {
            "testMessage": "Test message"
          }
        }
      }
    },
    "pvaChatbot": {
      "pageUrl": "https://<YOUR_SHAREPOINT_DOMAIN>.sharepoint.com/sites/<YOUR_SHAREPOINT_SITE>",
      "customActions": {
        "8541004f-b8f6-4863-a3fd-0721ec95f7cd": {
          "location": "ClientSideExtension.ApplicationCustomizer",
          "properties": {
            "testMessage": "Test message"
          }
        }
      }
    }
  }
}

Run the command below to serve locally and test

gulp serve

Screenshot of PVA Chatbot in Demo SharePoint site

If you see "Unable to connect" error message in the popup window, it means your BOT_ID is incorrect. Double check your BOT_ID on line 25 of the Chatbot.tsx file.

5. Deploy on SharePoint

Run the following commands to build your SPFx extension

gulp bundle --ship
gulp package-solution --ship

You should see a ".sppkg" file being generated in your "sharepoint/solution" folder. Upload this file to your tenant's SharePoint App Catalog.

Head over to your SharePoint site that you want to add this Chatbot to. Click on "+ New > App" and then select the newly uploaded extension file and you should see "Open Chatbot" button at bottom of your SharePoint page. Adding SPFx extension App to SharePoint site Refer to the official SPFx extension deployment guide if you are stuck.

Conclusion

This is only the first step, from there you can extend more functionality to your PVA chatbot. Link it up to Power Automate and you achieve some pretty complex features, such as searching for documents, finding people in Azure AD or even link up to external API via Power Automate connectors.

Want to customise your bot UI on the extension? Want to build more topics on PVA but not sure how? Have more complex use cases but not sure how to build it? Feel free to reach out!