Custom Copilot Studio
Neil Haddley • March 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 called the Copilot "Customer Copilot"

I watched the animation

I created a Copilot Studio Topic

I named the Topic "Parameter Config"

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_City" Global variable

I created a "Customer_No" Global variable

I created a "Customer_Blocked" Global variable

I updated the "Conversation Start" Topic

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

I saved the Topic

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

The Conversational boosting Topic was added

In the Conversational booking Topic I created a Custom Data variable

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

The Custom data datasource was set to the CustomData variable

I published 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" Alpine Ski House

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}