diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al index a6598918ab..22c284f72e 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al @@ -19,6 +19,7 @@ codeunit 324 "No. Series Copilot Impl." var IncorrectCompletionErr: Label 'Incorrect completion. The property %1 is empty', Comment = '%1 = property name'; + EmptyCompletionErr: Label 'Incorrect completion. The completion is empty.'; IncorrectCompletionNumberOfGeneratedNoSeriesErr: Label 'Incorrect completion. The number of generated number series is incorrect. Expected %1, but got %2', Comment = '%1 = Expected Number, %2 = Actual Number'; TextLengthIsOverMaxLimitErr: Label 'The property %1 exceeds the maximum length of %2', Comment = '%1 = property name, %2 = maximum length'; DateSpecificPlaceholderLbl: Label '{current_date}', Locked = true; @@ -68,6 +69,7 @@ codeunit 324 "No. Series Copilot Impl." procedure ApplyGeneratedNoSeries(var GeneratedNoSeries: Record "No. Series Generation Detail") begin + GeneratedNoSeries.SetRange(Exists, false); if GeneratedNoSeries.FindSet() then repeat InsertNoSeriesWithLines(GeneratedNoSeries); @@ -189,13 +191,13 @@ codeunit 324 "No. Series Copilot Impl." CompletionAnswerTxt := AOAIChatMessages.GetLastMessage(); // the model can answer to rephrase the question, if the user input is not clear if AOAIOperationResponse.IsFunctionCall() then - CompletionAnswerTxt := GenerateNoSeriesUsingToolResult(AzureOpenAI, InputText, AOAIOperationResponse); + CompletionAnswerTxt := GenerateNoSeriesUsingToolResult(AzureOpenAI, InputText, AOAIOperationResponse, AddNoSeriesIntent.GetExistingNoSeries()); exit(CompletionAnswerTxt); end; [NonDebuggable] - local procedure GenerateNoSeriesUsingToolResult(var AzureOpenAI: Codeunit "Azure OpenAI"; InputText: Text; var AOAIOperationResponse: Codeunit "AOAI Operation Response"): Text + local procedure GenerateNoSeriesUsingToolResult(var AzureOpenAI: Codeunit "Azure OpenAI"; InputText: Text; var AOAIOperationResponse: Codeunit "AOAI Operation Response"; ExistingNoSeriesArray: Text): Text var AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params"; AOAIChatMessages: Codeunit "AOAI Chat Messages"; @@ -209,6 +211,9 @@ codeunit 324 "No. Series Copilot Impl." FunctionResponses: List of [Codeunit "AOAI Function Response"]; Progress: Dialog; begin + if ExistingNoSeriesArray <> '' then + FinalResults.Add(ExistingNoSeriesArray); + FunctionResponses := AOAIOperationResponse.GetFunctionResponses(); foreach AOAIFunctionResponse in FunctionResponses do begin @@ -237,7 +242,6 @@ codeunit 324 "No. Series Copilot Impl." Progress.Close(); end; end; - exit(ConcatenateToolResponse(FinalResults)); end; @@ -337,6 +341,7 @@ codeunit 324 "No. Series Copilot Impl." begin ReadGeneratedNumberSeriesJArray(GeneratedNoSeriesArrayText).WriteTo(NoSeriesArrText); Json.InitializeCollection(NoSeriesArrText); + CheckIfArrayIsNotEmpty(Json.GetCollectionCount()); for i := 0 to Json.GetCollectionCount() - 1 do begin Json.GetObjectFromCollectionByIndex(i, NoSeriesObj); @@ -356,6 +361,12 @@ codeunit 324 "No. Series Copilot Impl." end; end; + local procedure CheckIfArrayIsNotEmpty(NumberOfGeneratedNoSeries: Integer) + begin + if NumberOfGeneratedNoSeries = 0 then + Error(EmptyCompletionErr); + end; + local procedure CheckTextPropertyExistAndCheckIfNotEmpty(propertyName: Text; var Json: Codeunit Json) var value: Text; @@ -420,47 +431,32 @@ codeunit 324 "No. Series Copilot Impl." var Json: Codeunit Json; i: Integer; - NoSeriesObj: Text; NoSeriesCodes: List of [Text]; - NoSeriesCode: Text; begin Json.InitializeCollection(NoSeriesArrText); - for i := 0 to Json.GetCollectionCount() - 1 do begin - Json.GetObjectFromCollectionByIndex(i, NoSeriesObj); - Json.InitializeObject(NoSeriesObj); - Json.GetStringPropertyValueByName('seriesCode', NoSeriesCode); - if NoSeriesCodes.Contains(NoSeriesCode) then begin - Json.ReplaceOrAddJPropertyInJObject('seriesCode', GenerateNewSeriesCodeValue(NoSeriesCodes, NoSeriesCode)); - NoSeriesObj := Json.GetObjectAsText(); - Json.ReplaceJObjectInCollection(i, NoSeriesObj); - end; - NoSeriesCodes.Add(NoSeriesCode); - end; + for i := 0 to Json.GetCollectionCount() - 1 do + ProcessNoSeries(i, NoSeriesCodes, Json); NoSeriesArrText := Json.GetCollectionAsText() end; - local procedure GenerateNewSeriesCodeValue(var NoSeriesCodes: List of [Text]; var NoSeriesCode: Text): Text + local procedure ProcessNoSeries(i: Integer; var NoSeriesCodes: List of [Text]; var Json: Codeunit Json) var - NewNoSeriesCode: Text; - begin - repeat - NewNoSeriesCode := CopyStr(NoSeriesCode, 1, 18) + '-' + RandomCharacter(); - until not NoSeriesCodes.Contains(NewNoSeriesCode); - - NoSeriesCode := NewNoSeriesCode; - exit(NewNoSeriesCode); - end; - - local procedure RandomCharacter(): Char + NoSeriesCode: Text; + NoSeriesObj: Text; + IsExists: Boolean; begin - exit(RandIntInRange(33, 126)); // ASCII: ! (33) to ~ (126) - end; + Json.GetObjectFromCollectionByIndex(i, NoSeriesObj); + Json.InitializeObject(NoSeriesObj); + Json.GetBoolPropertyValueFromJObjectByName('exists', IsExists); + Json.GetStringPropertyValueByName('seriesCode', NoSeriesCode); - local procedure RandIntInRange(MinInt: Integer; MaxInt: Integer): Integer - begin - exit(MinInt - 1 + Random(MaxInt - MinInt + 1)); + if NoSeriesCodes.Contains(NoSeriesCode) and (not IsExists) then begin + Json.RemoveJObjectFromCollection(i); + exit; + end; + NoSeriesCodes.Add(NoSeriesCode); end; local procedure InsertGeneratedNoSeries(var GeneratedNoSeries: Record "No. Series Generation Detail"; NoSeriesObj: Text; GenerationNo: Integer) @@ -482,7 +478,9 @@ codeunit 324 "No. Series Copilot Impl." Json.GetValueAndSetToRecFieldNo(RecRef, 'tableId', GeneratedNoSeries.FieldNo("Setup Table No.")); Json.GetValueAndSetToRecFieldNo(RecRef, 'fieldId', GeneratedNoSeries.FieldNo("Setup Field No.")); Json.GetValueAndSetToRecFieldNo(RecRef, 'nextYear', GeneratedNoSeries.FieldNo("Is Next Year")); - RecRef.Insert(true); + Json.GetValueAndSetToRecFieldNo(RecRef, 'exists', GeneratedNoSeries.FieldNo(Exists)); + Json.GetValueAndSetToRecFieldNo(RecRef, 'message', GeneratedNoSeries.FieldNo(Message)); + if RecRef.Insert(true) then; ValidateGeneratedNoSeries(RecRef); end; @@ -528,7 +526,7 @@ codeunit 324 "No. Series Copilot Impl." local procedure MaxModelTokens(): Integer begin - exit(16385); //gpt-4o-mini-latest + exit(16385); //gpt-4o-latest end; procedure IsCopilotVisible(): Boolean diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationDetail.Table.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationDetail.Table.al index 59b13d0246..a6c0f25a09 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationDetail.Table.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationDetail.Table.al @@ -5,6 +5,8 @@ namespace Microsoft.Foundation.NoSeries; +using System.Reflection; + table 392 "No. Series Generation Detail" { TableType = Temporary; @@ -76,6 +78,28 @@ table 392 "No. Series Generation Detail" { Caption = 'Starting Date'; } + field(13; "Exists"; Boolean) + { + Caption = 'Exists'; + } + field(14; Message; Text[1024]) + { + Caption = 'Message'; + } + field(20; "Setup Table Name"; Text[80]) + { + Caption = 'Setup Table'; + FieldClass = FlowField; + CalcFormula = lookup("Table Metadata".Caption where(ID = field("Setup Table No."))); + Editable = false; + } + field(21; "Setup Field Name"; Text[250]) + { + Caption = 'Setup Field'; + FieldClass = FlowField; + CalcFormula = lookup(Field."Field Caption" where(TableNo = field("Setup Table No."), "No." = field("Setup Field No."))); + Editable = false; + } } keys @@ -129,4 +153,4 @@ table 392 "No. Series Generation Detail" Rec."Starting Date" := CalcDate('<-CY+1Y>', Today); end; -} \ No newline at end of file +} diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationSub.Page.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationSub.Page.al index 2359753161..e3280509ff 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationSub.Page.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesGenerationSub.Page.al @@ -28,41 +28,76 @@ page 333 "No. Series Generation Sub" { ApplicationArea = All; ToolTip = 'Specifies the value of the Series Code field.'; + Enabled = IsEnabled; } field(Description; Rec.Description) { ApplicationArea = All; ToolTip = 'Specifies the value of the Description field.'; + Enabled = IsEnabled; } field("Starting No."; Rec."Starting No.") { ApplicationArea = All; ToolTip = 'Specifies the value of the Starting No. field.'; + Enabled = IsEnabled; } field("Increment-by No."; Rec."Increment-by No.") { ApplicationArea = All; ToolTip = 'Specifies the value of the Increment-by No. field.'; + Enabled = IsEnabled; } field("Ending No."; Rec."Ending No.") { ApplicationArea = All; ToolTip = 'Specifies the value of the Ending No. field.'; + Enabled = IsEnabled; } field("Warning No."; Rec."Warning No.") { ApplicationArea = All; ToolTip = 'Specifies the value of the Warning No. field.'; + Enabled = IsEnabled; } field("Starting Date"; Rec."Starting Date") { ApplicationArea = All; ToolTip = 'Specifies the value of the Starting Date field.'; + Enabled = IsEnabled; } + field(Message; Rec.Message) + { + ApplicationArea = All; + ToolTip = 'Specifies the value of the Message field.'; + Style = Attention; + Editable = false; + } + field("Setup Table Name"; Rec."Setup Table Name") + { + ApplicationArea = All; + ToolTip = 'Specifies the value of the Setup Table Name field.'; + Enabled = IsEnabled; + } + field("Setup Field Name"; Rec."Setup Field Name") + { + ApplicationArea = All; + ToolTip = 'Specifies the value of the Setup Field Name field.'; + Enabled = IsEnabled; + } + } } } + var + IsEnabled: Boolean; + + trigger OnAfterGetRecord() + begin + IsEnabled := not Rec.Exists; + end; + internal procedure Load(var GeneratedNoSeries: Record "No. Series Generation Detail") begin GeneratedNoSeries.Reset(); diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al index d6b3aa66d7..b2377be60d 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al @@ -20,6 +20,7 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function" AzureKeyVault: Codeunit "Azure Key Vault"; Telemetry: Codeunit Telemetry; ToolsImpl: Codeunit "No. Series Cop. Tools Impl."; + ExistingNoSeriesJArr: JsonArray; FunctionNameLbl: Label 'CreateNewNumberSeries', Locked = true; DateSpecificPlaceholderLbl: Label '{current_date}', Locked = true; CustomPatternsPlaceholderLbl: Label '{custom_patterns}', Locked = true; @@ -28,6 +29,7 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function" TelemetryTool1PromptRetrievalErr: Label 'Unable to retrieve the prompt for No. Series Copilot Tool 1 from Azure Key Vault.', Locked = true; TelemetryTool1DefinitionRetrievalErr: Label 'Unable to retrieve the definition for No. Series Copilot Tool 1 from Azure Key Vault.', Locked = true; ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 1. Please try again later.'; + ExistingNoSeriesMessageLbl: Label 'Number series already configured. If you wish to modify the existing series, please use the `Modify number series` prompt.'; procedure GetName(): Text begin @@ -148,8 +150,10 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function" exit; FieldRef := RecRef.Field(Field."No."); - if Format(FieldRef.Value) <> '' then + if Format(FieldRef.Value) <> '' then begin + SaveExistingNoSeries(TempTableMetadata, FieldRef); exit; // No need to generate number series if it already created and configured + end; TempSetupTable := TempTableMetadata; if TempSetupTable.Insert() then; @@ -158,6 +162,42 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function" TempNoSeriesField.Insert(); end; + local procedure SaveExistingNoSeries(TempTableMetadata: Record "Table Metadata" temporary; FieldRef: FieldRef) + var + NoSeries: Record "No. Series"; + NoSeriesLine: Record "No. Series Line"; + NoSeriesManagement: Codeunit "No. Series"; + ExistingNoSeriesJObj: JsonObject; + begin + if not NoSeries.Get(Format(FieldRef.Value)) then + exit; + + NoSeriesManagement.GetNoSeriesLine(NoSeriesLine, NoSeries.Code, Today(), false); + + Clear(ExistingNoSeriesJObj); + ExistingNoSeriesJObj.Add('seriesCode', NoSeries.Code); + ExistingNoSeriesJObj.Add('description', NoSeries.Description); + ExistingNoSeriesJObj.Add('startingNo', NoSeriesLine."Starting No."); + ExistingNoSeriesJObj.Add('endingNo', NoSeriesLine."Ending No."); + ExistingNoSeriesJObj.Add('warningNo', NoSeriesLine."Warning No."); + ExistingNoSeriesJObj.Add('incrementByNo', NoSeriesLine."Increment-by No."); + ExistingNoSeriesJObj.Add('tableId', TempTableMetadata.ID); + ExistingNoSeriesJObj.Add('fieldId', FieldRef.Number); + ExistingNoSeriesJObj.Add('nextYear', false); + ExistingNoSeriesJObj.Add('exists', true); + ExistingNoSeriesJObj.Add('message', ExistingNoSeriesMessageLbl); + + ExistingNoSeriesJArr.Add(ExistingNoSeriesJObj); + end; + + procedure GetExistingNoSeries() ExistingNoSeries: Text + begin + if ExistingNoSeriesJArr.Count() = 0 then + exit(''); + + ExistingNoSeriesJArr.WriteTo(ExistingNoSeries); + end; + [NonDebuggable] local procedure GetToolPrompt() Prompt: Text begin diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al index 4b97e95cef..7fc97a5beb 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al @@ -234,13 +234,18 @@ codeunit 336 "No. Series Cop. Tools Impl." foreach Entity in Entities do begin String1 := RecordMatchMgtCopy.RemoveShortWords(RemoveTextPart(TableMetadata.Caption, ' Setup') + ' ' + RemoveTextParts(Field.FieldName, GetNoSeriesAbbreviations())); String2 := RecordMatchMgtCopy.RemoveShortWords(Entity); - Score := RecordMatchMgtCopy.CalculateStringNearness(String1, String2, 1, 100) / 100; + Score := RecordMatchMgtCopy.CalculateStringNearness(String1, String2, GetMatchLengthThreshold(), 100) / 100; if Score >= RequiredNearness() then exit(true); end; exit(false); end; + local procedure GetMatchLengthThreshold(): Decimal + begin + exit(2); + end; + local procedure RequiredNearness(): Decimal begin exit(0.9)