Custom Copilot Studio

Neil HaddleyMarch 23, 2024

Customer Copilot

I created a Business Central extension that allows Business Central users to "chat to" Customers.

I used the Global Variable and Generative AI features in Copilot studio.

The same approach could be used to allow users to chat to other Business Central entities

I created a new Copilot

I created a new Copilot

I called the Copilot "Customer Copilot"

I called the Copilot "Customer Copilot"

I watched the animation

I watched the animation

I created a Copilot Studio Topic

I created a Copilot Studio Topic

I named the Topic "Parameter Config"

I named the Topic "Parameter Config"

I used the "Set a variable value" action

I used the "Set a variable value" action

I created a "Customer_Name" Global variable that could be set by an external source (Business Central in this case)

I created a "Customer_Name" Global variable that could be set by an external source (Business Central in this case)

I created a "Customer_City" Global variable

I created a "Customer_City" Global variable

I created a "Customer_No" Global variable

I created a "Customer_No" Global variable

I created a "Customer_Blocked" Global variable

I created a "Customer_Blocked" Global variable

I updated the "Conversation Start" Topic

I updated the "Conversation Start" Topic

I used the {x} button to insert Global variable values into the Conversation Start action's text

I used the {x} button to insert Global variable values into the Conversation Start action's text

I saved the Topic

I saved the Topic

I navigated to the Generative AI tab and enabled the "Boost conversational coverage with generative answers" feature

I navigated to the Generative AI tab and enabled the "Boost conversational coverage with generative answers" feature

The Conversational boosting Topic was added

The Conversational boosting Topic was added

In the Conversational booking Topic I created a Custom Data variable

In the Conversational booking Topic I created a Custom Data variable

I updated the "create generative answers" action's Data sources property

I updated the "create generative answers" action's Data sources property

The Custom data datasource was set to the CustomData variable

The Custom data datasource was set to the CustomData variable

I published the Copilot

I published the Copilot

I created a Business Central extension that connected to the Copilot

I created a Business Central extension that connected to the Copilot

I was able to "chat to" School of Fine Art

I was able to "chat to" School of Fine Art

I was able to "chat to" Alpine Ski House

I was able to "chat to" Alpine Ski House

I was able to "chat to" Adatum Corporation

I was able to "chat to" Adatum Corporation

CustomerCopilotExtension.al

TEXT
1controladdin CustomerCopilotFactBox
2{
3    RequestedHeight = 400;
4    MaximumHeight = 600;
5    VerticalStretch = false;
6    VerticalShrink = false;
7    HorizontalStretch = true;
8    HorizontalShrink = true;
9    Scripts = 'https://cdn.botframework.com/botframework-webchat/latest/webchat.js', 'CustomerCopilotFunctions.js';
10    Stylesheets = 'CustomerCopilotStyles.css';
11
12    event ControlReady()
13    procedure CreateSidebar(no: Text; city: Text; name: Text; blocked: Enum Microsoft.Sales.Customer."Customer Blocked");
14}
15
16page 99101 CopilotCustListFactBoxPart
17{
18    PageType = CardPart;
19    SourceTable = "Customer";
20
21    layout
22    {
23        area(Content)
24        {
25            usercontrol(Sidebar; CustomerCopilotFactBox)
26            {
27                ApplicationArea = All;
28            }
29        }
30    }
31
32    trigger OnAfterGetRecord()
33    begin
34        CurrPage.Sidebar.CreateSidebar(rec."No.", rec.City, rec.Name, rec.Blocked);
35    end;
36}
37
38pageextension 99102 "Customer Copilot Cust Card" extends "Customer Card"
39{
40    layout
41    {
42        addfirst(FactBoxes)
43        {
44            part(CopilotCustListFactBoxPart; CopilotCustListFactBoxPart)
45            {
46                ApplicationArea = all; //Basic, Suite;// all;
47                Caption = 'Customer Copilot';
48                SubPageLink = "No." = FIELD("No.");
49            }
50        }
51    }
52
53}
54
55pageextension 99103 "Customer Copilot Cust List" extends "Customer List"
56{
57
58    layout
59    {
60        addfirst(FactBoxes)
61        {
62            part(CopilotCustListFactBoxPart; CopilotCustListFactBoxPart)
63            {
64                ApplicationArea = all;
65                caption = 'Customer Copilot';
66                SubPageLink = "No." = FIELD("No.");
67            }
68        }
69
70    }
71}

CustomerCopilotFunctions.js

TEXT
1function CreateSidebar(no, city, name, blocked) {
2    var __div = document.getElementById('controlAddIn');
3
4    (async function () {
5
6        // Specifies style options to customize the Web Chat canvas.
7        // Please visit https://microsoft.github.io/BotFramework-WebChat for customization samples.
8        const styleOptions = {
9            accent: '#00809d',
10            botAvatarBackgroundColor: '#FFFFFF',
11            botAvatarImage: 'data:image;base64,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',
12            botAvatarInitials: 'BOT',
13            userAvatarImage: 'https://content.powerapps.com/resource/makerx/static/media/user.336bbce3.svg',
14            userAvatarInitials: 'USER',
15            hideUploadButton: true,
16            primaryFont: fontFamily(['Segoe UI', 'Segoe WP', 'Segoe', 'device-segoe', 'Tahoma', 'Helvetica', 'Arial', 'sans-serif']),
17        };
18
19        // Specifies the token endpoint URL.
20        // To get this value, visit Copilot Studio > Settings > Channels > Mobile app page.
21        const tokenEndpointURL = new URL('https://b838e0443024ea32b2f47862b85e99.03.environment.api.powerplatform.com/powervirtualagents/botsbyschema/cr74e_customerCopilot/directline/token?api-version=2022-03-01-preview');
22
23        const locale = document.documentElement.lang || 'en'; // Uses language specified in <html> element and fallback to English (United States).
24
25        const apiVersion = tokenEndpointURL.searchParams.get('api-version');
26
27        const [directLineURL, token] = await Promise.all([
28            fetch(new URL(`/powervirtualagents/regionalchannelsettings?api-version=${apiVersion}`, tokenEndpointURL))
29                .then(response => {
30                    if (!response.ok) {
31                        throw new Error('Failed to retrieve regional channel settings.');
32                    }
33
34                    return response.json();
35                })
36                .then(({ channelUrlsById: { directline } }) => directline),
37            fetch(tokenEndpointURL)
38                .then(response => {
39                    if (!response.ok) {
40                        throw new Error('Failed to retrieve Direct Line token.');
41                    }
42
43                    return response.json();
44                })
45                .then(({ token }) => token)
46        ]);
47
48        const directLine = WebChat.createDirectLine({ domain: new URL('v3/directline', directLineURL), token });
49
50        // Sends "startConversation" event when the connection is established.
51        const subscription = directLine.connectionStatus$.subscribe({
52            next(value) {
53                if (value === 2) {
54                    directLine
55                        .postActivity({
56                            localTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
57                            locale,
58                            name: 'startConversation',
59                            type: 'event',
60                            // https://www.dynamicsforcrm.com/pass-parameters-to-copilot-studio-bot-from-calling-site/
61                            value: {
62                                Customer_No:no, 
63                                Customer_City:city, 
64                                Customer_Name:name, 
65                                Customer_Blocked:blocked
66                            }
67                        })
68                        .subscribe();
69
70                    // Only send the event once, unsubscribe after the event is sent.
71                    subscription.unsubscribe();
72                }
73            }
74        });
75
76        WebChat.renderWebChat({ directLine, locale, styleOptions }, __div);
77
78
79        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod("ControlReady");
80
81    })();
82
83}
84
85function fontFamily(fonts) {
86    return fonts.map(font => `'${font}'`).join(', ');
87}

CustomerCopilotStyles.css

TEXT
1* {
2    font-size: 10.5pt
3}