Skip to content

Commit

Permalink
[AI] Monetization notifications (#2901)
Browse files Browse the repository at this point in the history
<!-- Thank you for submitting a Pull Request. If you're new to
contributing to BCApps please read our pull request guideline below
* https://github.com/microsoft/BCApps/Contributing.md
-->
#### Summary <!-- Provide a general summary of your changes -->
Notifications being added:
- You may be charged in the future
- You're running out of AI quota
- You're out of AI quota


![image](https://github.com/user-attachments/assets/cfa5cd15-bd3a-43b2-ac8f-303bf27f0443)

![image](https://github.com/user-attachments/assets/3ebcecc9-2b67-4000-b747-bc51e7a8ad18)

![image](https://github.com/user-attachments/assets/b77d34dd-2778-480d-be49-ad9d942207ca)

When the `Learn More` is pressed, a non-admin user will be directed to
the docs.
An admin will see the following dialogs 

![image](https://github.com/user-attachments/assets/a3ad66ca-1b1e-4bf3-b05c-5b51af3c317a)

![image](https://github.com/user-attachments/assets/7acc8505-e528-4a91-a7b6-268f599c79aa)

The notification will also show when you run `generate`

![image](https://github.com/user-attachments/assets/c4a7f737-c4b8-4018-8460-4b362e7c90fd)

![image](https://github.com/user-attachments/assets/906eebe6-8745-41cb-9dac-d14cf08201ad)

#### Work Item(s) <!-- Add the issue number here after the #. The issue
needs to be open and approved. Submitting PRs with no linked issues or
unapproved issues is highly discouraged. -->
Fixes
[AB#563277](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/563277)
  • Loading branch information
darjoo authored Feb 10, 2025
1 parent a636ffb commit 22e9e48
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 37 deletions.
2 changes: 1 addition & 1 deletion src/System Application/App/AI/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"platform": "26.0.0.0",
"idRanges": [
{
"from": 7758,
"from": 7757,
"to": 7778
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ codeunit 7772 "Azure OpenAI Impl"
[NonDebuggable]
local procedure SendRequest(ModelType: Enum "AOAI Model Type"; AOAIAuthorization: Codeunit "AOAI Authorization"; Payload: Text; var AOAIOperationResponse: Codeunit "AOAI Operation Response"; CallerModuleInfo: ModuleInfo)
var
CopilotNotifications: Codeunit "Copilot Notifications";
ALCopilotAuthorization: DotNet ALCopilotAuthorization;
ALCopilotCapability: DotNet ALCopilotCapability;
ALCopilotFunctions: DotNet ALCopilotFunctions;
Expand Down Expand Up @@ -548,6 +549,9 @@ codeunit 7772 "Azure OpenAI Impl"
Error := GetLastErrorText();
AOAIOperationResponse.SetOperationResponse(ALCopilotOperationResponse.IsSuccess(), ALCopilotOperationResponse.StatusCode(), ALCopilotOperationResponse.Result(), Error);

if AOAIOperationResponse.GetStatusCode() = 402 then
CopilotNotifications.CheckAIQuotaAndShowNotification();

if not ALCopilotOperationResponse.IsSuccess() then
Error(GenerateRequestFailedErr);
end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ page 7775 "Copilot AI Capabilities"

trigger OnOpenPage()
var
CopilotNotifications: Codeunit "Copilot Notifications";
EnvironmentInformation: Codeunit "Environment Information";
WithinGeo: Boolean;
WithinEUDB: Boolean;
Expand All @@ -272,10 +273,10 @@ page 7775 "Copilot AI Capabilities"
CurrPage.EarlyPreviewCapabilities.Page.SetDataMovement(AllowDataMovement);

if not EnvironmentInformation.IsSaaSInfrastructure() then
CopilotCapabilityImpl.ShowCapabilitiesNotAvailableOnPremNotification();
CopilotNotifications.ShowCapabilitiesNotAvailableOnPremNotification();

if (WithinGeo and not WithinEUDB) and (not AllowDataMovement) then
CopilotCapabilityImpl.ShowPrivacyNoticeDisagreedNotification();
CopilotNotifications.ShowPrivacyNoticeDisagreedNotification();

CopilotCapabilityImpl.UpdateGuidedExperience(AllowDataMovement);

Expand All @@ -284,6 +285,11 @@ page 7775 "Copilot AI Capabilities"
WithinEUDBArea := WithinEUDB;
WithinAOAIServicesInRegionArea := WithinGeo and (not WithinEUDB);
WithinAOAIOutOfRegionArea := (not WithinGeo) and (not WithinEUDB);

if EnvironmentInformation.IsSaaSInfrastructure() then begin
CopilotNotifications.ShowBillingInTheFutureNotification();
CopilotNotifications.CheckAIQuotaAndShowNotification();
end;
end;

local procedure HasEarlyPreviewCapabilities(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,11 @@ codeunit 7774 "Copilot Capability Impl"
AzureOpenAiTxt: Label 'Azure OpenAI', Locked = true;
AlreadyRegisteredErr: Label 'Capability has already been registered.';
NotRegisteredErr: Label 'Copilot capability has not been registered by the module.';
ReviewPrivacyNoticeLbl: Label 'Review the privacy notice';
PrivacyNoticeDisagreedNotificationMessageLbl: Label 'To enable Copilot, please review and accept the privacy notice.';
CapabilitiesNotAvailableOnPremNotificationMessageLbl: Label 'Copilot capabilities published by Microsoft are not available on-premises. You can extend Copilot with custom capabilities and use them on-premises for development purposes only.';
TelemetryRegisteredNewCopilotCapabilityLbl: Label 'New copilot capability registered.', Locked = true;
TelemetryModifiedCopilotCapabilityLbl: Label 'Copilot capability modified', Locked = true;
TelemetryUnregisteredCopilotCapabilityLbl: Label 'Copilot capability unregistered.', Locked = true;
TelemetryActivatedCopilotCapabilityLbl: Label 'Copilot capability activated.', Locked = true;
TelemetryDeactivatedCopilotCapabilityLbl: Label 'Copilot capability deactivated.', Locked = true;
NotificationPrivacyNoticeDisagreedLbl: Label 'bd91b436-29ba-4823-824c-fc926c9842c2', Locked = true;
NotificationCapabilitiesNotAvailableOnPremLbl: Label 'ada1592d-9728-485c-897e-8d18e8dd7dee', Locked = true;


procedure RegisterCapability(CopilotCapability: Enum "Copilot Capability"; LearnMoreUrl: Text[2048]; CallerModuleInfo: ModuleInfo)
begin
Expand Down Expand Up @@ -181,34 +175,6 @@ codeunit 7774 "Copilot Capability Impl"
GlobalLanguage(SavedGlobalLanguageId);
end;

procedure ShowPrivacyNoticeDisagreedNotification()
var
Notification: Notification;
NotificationGuid: Guid;
begin
NotificationGuid := NotificationPrivacyNoticeDisagreedLbl;
Notification.Id(NotificationGuid);
Notification.Message(PrivacyNoticeDisagreedNotificationMessageLbl);
Notification.AddAction(ReviewPrivacyNoticeLbl, Codeunit::"Copilot Capability Impl", 'OpenPrivacyNotice');
Notification.Send();
end;

procedure ShowCapabilitiesNotAvailableOnPremNotification()
var
Notification: Notification;
NotificationGuid: Guid;
begin
NotificationGuid := NotificationCapabilitiesNotAvailableOnPremLbl;
Notification.Id(NotificationGuid);
Notification.Message(CapabilitiesNotAvailableOnPremNotificationMessageLbl);
Notification.Send();
end;

procedure OpenPrivacyNotice(Notification: Notification)
begin
Page.Run(Page::"Privacy Notices");
end;

procedure GetAzureOpenAICategory(): Code[50]
begin
exit(AzureOpenAiTxt);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace System.AI;

using System;
using System.Privacy;

codeunit 7757 "Copilot Notifications"
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
CopilotCapabilityImpl: Codeunit "Copilot Capability Impl";
ReviewPrivacyNoticeLbl: Label 'Review the privacy notice';
PrivacyNoticeDisagreedNotificationMessageLbl: Label 'To enable Copilot, please review and accept the privacy notice.';
CapabilitiesNotAvailableOnPremNotificationMessageLbl: Label 'Copilot capabilities published by Microsoft are not available on-premises. You can extend Copilot with custom capabilities and use them on-premises for development purposes only.';
NotificationPrivacyNoticeDisagreedLbl: Label 'bd91b436-29ba-4823-824c-fc926c9842c2', Locked = true;
NotificationCapabilitiesNotAvailableOnPremLbl: Label 'ada1592d-9728-485c-897e-8d18e8dd7dee', Locked = true;
BillingInTheFutureNotificationGuidTok: Label 'cb577f99-d252-4de7-a1ab-922ac2af12b7', Locked = true;
BillingInTheFutureNotificationMsg: Label 'By activating AI capabilities, you understand your organization may be billed for its use in the future.';
BillingInTheFutureLearnMoreLinkLbl: Label 'https://go.microsoft.com/fwlink/?linkid=2302317', Locked = true;
AIQuotaUsedUpNotificationGuidTok: Label 'eced148b-4721-4ff9-b4c8-a8b5b1209692', Locked = true;
AIQuotaUsedUpNotificationMsg: Label 'AI capabilities are currently unavailable because your organization has used up its AI quota.';
AIQuotaUsedUpLearnMoreLinkLbl: Label 'https://go.microsoft.com/fwlink/?linkid=2302511', Locked = true;
AIQuotaUsedUpAdminMsg: Label 'AI capabilities in Business Central require AI quota.\\Your organization has used up its AI quota, so AI capabilities are currently unavailable.\\Would you like to open the Business Central administration center to learn more about AI quota?';
AIQuotaNearlyUsedUpNotificationGuidTok: Label '4a15b17c-1f88-4cc6-a342-4300ba400c8a', Locked = true;
AIQuotaNearlyUsedUpNotificationMsg: Label 'The AI quota in this environment is nearly used up. When it is, AI capabilities will be unavailable.';
AIQuotaNearlyUsedUpLearnMoreLinkLbl: Label 'https://go.microsoft.com/fwlink/?linkid=2302603', Locked = true;
AIQuotaNearlyUsedUpAdminMsg: Label 'AI capabilities in Business Central require AI quota, and your organization has a limited amount remaining.\\When it''s used up, AI capabilities will be unavailable until AI quota is available again.\\Would you like to open the Business Central administration center to learn more about AI quota?';
BCAdminCenterSaaSLinkTxt: Label '%1/admin', Comment = '%1 - BC url', Locked = true;
LearnMoreLbl: Label 'Learn more', Locked = true;

procedure CheckAIQuotaAndShowNotification()
var
ALCopilotFunctions: DotNet ALCopilotFunctions;
ALCopilotQuotaDetails: Dotnet ALCopilotQuotaDetails;
begin
ALCopilotQuotaDetails := ALCopilotFunctions.GetCopilotQuotaDetails();

if IsNull(ALCopilotQuotaDetails) then
exit;

if not ALCopilotQuotaDetails.CanConsume() then begin
ShowAIQuotaUsedUpNotification();
exit;
end;

if ALCopilotQuotaDetails.HasSetupBilling() then
exit;

if ALCopilotQuotaDetails.QuotaUsedPercentage() >= 80.0 then
ShowAIQuotaNearlyUsedUpNotification();
end;

procedure ShowPrivacyNoticeDisagreedNotification()
var
Notification: Notification;
NotificationGuid: Guid;
begin
NotificationGuid := NotificationPrivacyNoticeDisagreedLbl;
Notification.Id(NotificationGuid);
Notification.Message(PrivacyNoticeDisagreedNotificationMessageLbl);
Notification.AddAction(ReviewPrivacyNoticeLbl, Codeunit::"Copilot Notifications", 'OpenPrivacyNotice');
Notification.Send();
end;

procedure OpenPrivacyNotice(Notification: Notification)
begin
Page.Run(Page::"Privacy Notices");
end;

procedure ShowCapabilitiesNotAvailableOnPremNotification()
var
Notification: Notification;
NotificationGuid: Guid;
begin
NotificationGuid := NotificationCapabilitiesNotAvailableOnPremLbl;
Notification.Id(NotificationGuid);
Notification.Message(CapabilitiesNotAvailableOnPremNotificationMessageLbl);
Notification.Send();
end;

procedure ShowBillingInTheFutureNotification()
var
BillingInTheFutureNotification: Notification;
begin
BillingInTheFutureNotification.Id := BillingInTheFutureNotificationGuidTok;
BillingInTheFutureNotification.Message := BillingInTheFutureNotificationMsg;
BillingInTheFutureNotification.Scope := NotificationScope::LocalScope;
BillingInTheFutureNotification.AddAction(LearnMoreLbl, Codeunit::"Copilot Notifications", 'ShowBillingInTheFutureLearnMore');
BillingInTheFutureNotification.Send();
end;

procedure ShowBillingInTheFutureLearnMore(BillingInTheFutureNotification: Notification)
begin
Hyperlink(BillingInTheFutureLearnMoreLinkLbl);
end;

procedure ShowAIQuotaUsedUpNotification()
var
AIQuotaUsedUpNotification: Notification;
begin
AIQuotaUsedUpNotification.Id := AIQuotaUsedUpNotificationGuidTok;
AIQuotaUsedUpNotification.Message := AIQuotaUsedUpNotificationMsg;
AIQuotaUsedUpNotification.Scope := NotificationScope::LocalScope;
AIQuotaUsedUpNotification.AddAction(LearnMoreLbl, Codeunit::"Copilot Notifications", 'ShowAIQuotaUsedUpLearnMore');
AIQuotaUsedUpNotification.Send();
end;

procedure ShowAIQuotaUsedUpLearnMore(AIQuotaUsedUpNotification: Notification)
begin
if CopilotCapabilityImpl.IsAdmin() then begin
if Dialog.Confirm(AIQuotaUsedUpAdminMsg) then
OpenBCAdminCenter();
end
else
Hyperlink(AIQuotaUsedUpLearnMoreLinkLbl);
end;

procedure ShowAIQuotaNearlyUsedUpNotification()
var
AIQuotaNearlyUsedUpNotification: Notification;
begin
AIQuotaNearlyUsedUpNotification.Id := AIQuotaNearlyUsedUpNotificationGuidTok;
AIQuotaNearlyUsedUpNotification.Message := AIQuotaNearlyUsedUpNotificationMsg;
AIQuotaNearlyUsedUpNotification.Scope := NotificationScope::LocalScope;
AIQuotaNearlyUsedUpNotification.AddAction(LearnMoreLbl, Codeunit::"Copilot Notifications", 'ShowAIQuotaNearlyUsedUpLearnMore');
AIQuotaNearlyUsedUpNotification.Send();
end;

procedure ShowAIQuotaNearlyUsedUpLearnMore(AIQuotaNearlyUsedUpNotification: Notification)
begin
if CopilotCapabilityImpl.IsAdmin() then begin
if Dialog.Confirm(AIQuotaNearlyUsedUpAdminMsg) then
OpenBCAdminCenter();
end
else
Hyperlink(AIQuotaNearlyUsedUpLearnMoreLinkLbl);
end;

local procedure OpenBCAdminCenter()
var
Url: Text;
begin
Url := GetUrl(ClientType::Web);
Url := StrSubstNo(BCAdminCenterSaaSLinkTxt, CopyStr(Url, 1, Url.LastIndexOf('/') - 1));
Hyperlink(Url);
end;
}
4 changes: 4 additions & 0 deletions src/System Application/App/DotNet Aliases/src/dotnet.al
Original file line number Diff line number Diff line change
Expand Up @@ -2137,6 +2137,10 @@ dotnet
type("Microsoft.Dynamics.Nav.Service.CopilotApi.AL.ALCopilotCapability"; ALCopilotCapability)
{
}

type("Microsoft.Dynamics.Nav.Service.CopilotApi.AL.ALCopilotQuotaDetails"; ALCopilotQuotaDetails)
{
}
}
assembly("Microsoft.Dynamics.Nav.DataSearch")
{
Expand Down

0 comments on commit 22e9e48

Please sign in to comment.