diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..a88e69e --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @DevExpressExampleBot \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Assistant.razor b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Assistant.razor index 858ed58..431e9a2 100644 --- a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Assistant.razor +++ b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Assistant.razor @@ -3,18 +3,29 @@ @using DevExpress.AIIntegration.Blazor.Chat @using AIIntegration.Services.Chat @using System.Reflection +@using Markdig - + + +
+ @ToHtml(context.Content) +
+
+
@code { const string DocumentResourceName = "DevExpress.AI.Samples.Blazor.Data.Restaurant Menu.pdf"; const string prompt = "You are an analytics assistant specialized in analyzing PDF files. Your role is to assist users by providing accurate answers to their questions about data contained within these files.\n \n### Tasks:\n- Perform various types of data analyses, including summaries, calculations, data filtering, and trend identification.\n- Clearly explain your analysis process to ensure users understand how you arrived at your answers.\n- Always provide precise and accurate information based on the Excel data.\n- If you cannot find an answer based on the provided data, explicitly state: \"The requested information cannot be found in the data provided.\"\n \n### Examples:\n1. **Summarization:**\n - **User Question:** \"What is the average sales revenue for Q1?\"\n - **Response:** \"The average sales revenue for Q1 is calculated as $45,000, based on the data in Sheet1, Column C.\"\n \n2. **Data Filtering:**\n - **User Question:** \"Which products had sales over $10,000 in June?\"\n - **Response:** \"The products with sales over $10,000 in June are listed in Sheet2, Column D, and they include Product A, Product B, and Product C.\"\n \n3. **Insufficient Data:**\n - **User Question:** \"What is the market trend for Product Z over the past 5 years?\"\n - **Response:** \"The requested information cannot be found in the data provided, as the dataset only includes data for the current year.\"\n \n### Additional Instructions:\n- Format your responses to clearly indicate which sheet and column the data was extracted from when necessary.\n- Avoid providing any answers if the data in the file is insufficient for a reliable response.\n- Ask clarifying questions if the user's query is ambiguous or lacks detail.\n \nRemember, your primary goal is to provide helpful, data-driven insights that directly answer the user's questions. Do not assume or infer information not present in the dataset."; async Task Initialized(IAIChat chat) { - await chat.UseAssistantAsync(new OpenAIAssistantOptions( + await chat.SetupAssistantAsync(new OpenAIAssistantOptions( $"{Guid.NewGuid().ToString("N")}.pdf", Assembly.GetExecutingAssembly().GetManifestResourceStream(DocumentResourceName), prompt) ); } + MarkupString ToHtml(string text) { + return (MarkupString)Markdown.ToHtml(text); + } + } } \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-CustomMessage.razor b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-CustomMessage.razor index fcc0714..0ea41f0 100644 --- a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-CustomMessage.razor +++ b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-CustomMessage.razor @@ -6,18 +6,18 @@
@if(context.Typing) { - Loading... + Loading... } else { -
- @context.Content -
+
+ @context.Content +
}
@code { - string GetMessageClasses(ChatMessage message) { + string GetMessageClasses(BlazorChatMessage message) { if(message.Role == ChatMessageRole.Assistant) { return "my-chat-message my-assistant-message"; } else if(message.Role == ChatMessageRole.User) { diff --git a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Markdown.razor b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Markdown.razor index 0eabd99..44ff59f 100644 --- a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Markdown.razor +++ b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-Markdown.razor @@ -3,7 +3,7 @@ @using DevExpress.AIIntegration.Blazor.Chat @using AIIntegration.Services.Chat; - +
@ToHtml(context.Content) diff --git a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-MessageSent.razor b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-MessageSent.razor index 79871cf..19d83f2 100644 --- a/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-MessageSent.razor +++ b/CS/DevExpress.AI.Samples.Blazor/Components/Pages/Chat-MessageSent.razor @@ -5,8 +5,7 @@ @code { - void MessageSent(MessageSentEventArgs args) { - var message = new Message(MessageRole.Assistant, $"Processed: {args.Content}"); - args.SendMessage(message); + async Task MessageSent(MessageSentEventArgs args) { + await args.Chat.SendMessage($"Processed: {args.Content}", Microsoft.Extensions.AI.ChatRole.Assistant); } } \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.Blazor/DevExpress.AI.Samples.Blazor.csproj b/CS/DevExpress.AI.Samples.Blazor/DevExpress.AI.Samples.Blazor.csproj index 90166b1..f383749 100644 --- a/CS/DevExpress.AI.Samples.Blazor/DevExpress.AI.Samples.Blazor.csproj +++ b/CS/DevExpress.AI.Samples.Blazor/DevExpress.AI.Samples.Blazor.csproj @@ -7,11 +7,13 @@ - - - - - + + + + + + + diff --git a/CS/DevExpress.AI.Samples.Blazor/Program.cs b/CS/DevExpress.AI.Samples.Blazor/Program.cs index 8f964c9..b56de48 100644 --- a/CS/DevExpress.AI.Samples.Blazor/Program.cs +++ b/CS/DevExpress.AI.Samples.Blazor/Program.cs @@ -1,7 +1,8 @@ -using Azure.AI.OpenAI; -using Azure; +using System.ClientModel; +using Azure.AI.OpenAI; using DevExpress.AI.Samples.Blazor.Components; using DevExpress.AIIntegration; +using Microsoft.Extensions.AI; var builder = WebApplication.CreateBuilder(args); @@ -11,14 +12,19 @@ string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); +string deploymentName = string.Empty; + +var azureClient = new AzureOpenAIClient( + new Uri(azureOpenAIEndpoint), + new ApiKeyCredential(azureOpenAIKey)); builder.Services.AddDevExpressBlazor(); +builder.Services.AddChatClient(cfg => + cfg.Use(azureClient.AsChatClient((deploymentName))) +); builder.Services.AddDevExpressAI((config) => { - var client = new AzureOpenAIClient( - new Uri(azureOpenAIEndpoint), - new AzureKeyCredential(azureOpenAIKey)); - config.RegisterChatClientOpenAIService(client, "gpt4o"); - config.RegisterOpenAIAssistants(client, "gpt4o"); + //Reference the DevExpress.AIIntegration.OpenAI NuGet package to use Open AI Asisstants + config.RegisterOpenAIAssistants(azureClient, "gpt4o"); }); var app = builder.Build(); diff --git a/CS/DevExpress.AI.Samples.MAUIBlazor/DevExpress.AI.Samples.MAUIBlazor.csproj b/CS/DevExpress.AI.Samples.MAUIBlazor/DevExpress.AI.Samples.MAUIBlazor.csproj index f879419..58c07b1 100644 --- a/CS/DevExpress.AI.Samples.MAUIBlazor/DevExpress.AI.Samples.MAUIBlazor.csproj +++ b/CS/DevExpress.AI.Samples.MAUIBlazor/DevExpress.AI.Samples.MAUIBlazor.csproj @@ -43,17 +43,18 @@ - + - - - - - + + + + + + diff --git a/CS/DevExpress.AI.Samples.MAUIBlazor/MainViewModel.cs b/CS/DevExpress.AI.Samples.MAUIBlazor/MainViewModel.cs index fef6459..f257a6f 100644 --- a/CS/DevExpress.AI.Samples.MAUIBlazor/MainViewModel.cs +++ b/CS/DevExpress.AI.Samples.MAUIBlazor/MainViewModel.cs @@ -1,6 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; - +using Microsoft.Extensions.AI; namespace DevExpress.AI.Samples.MAUIBlazor; partial class MainViewModel : ObservableObject { @@ -11,10 +11,8 @@ partial class MainViewModel : ObservableObject { [RelayCommand(CanExecute = nameof(CanSendMessage))] async Task SendMessageAsync() { var service = DxChatEncapsulationService.Instance; - service.DxChatUI.CurrentMessage = Message!; + await service.DxChatUI?.SendMessage(Message, ChatRole.User); Message = null; - if (service.DxChatUI.SendButton != null) - await service.DxChatUI.SendButton.Click.InvokeAsync(); } bool CanSendMessage() { diff --git a/CS/DevExpress.AI.Samples.MAUIBlazor/MauiProgram.cs b/CS/DevExpress.AI.Samples.MAUIBlazor/MauiProgram.cs index 4239131..40f3164 100644 --- a/CS/DevExpress.AI.Samples.MAUIBlazor/MauiProgram.cs +++ b/CS/DevExpress.AI.Samples.MAUIBlazor/MauiProgram.cs @@ -1,8 +1,9 @@ -using Azure; +using System.ClientModel; +using Azure; using Azure.AI.OpenAI; -using DevExpress.AIIntegration; using DevExpress.Maui; using DevExpress.Maui.Core; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; namespace DevExpress.AI.Samples.MAUIBlazor; @@ -24,13 +25,14 @@ public static MauiApp CreateMauiApp() { string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!; string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!; + var chatClient = new AzureOpenAIClient( + new Uri(azureOpenAIEndpoint), + new ApiKeyCredential(azureOpenAIKey)).AsChatClient("gpt4o"); + builder.Services.AddMauiBlazorWebView(); builder.Services.AddDevExpressBlazor(); builder.Services.AddDevExpressAI((config) => { - config.RegisterChatClientOpenAIService( - new AzureOpenAIClient( - new Uri(azureOpenAIEndpoint), - new AzureKeyCredential(azureOpenAIKey)), "gpt4o"); + config.RegisterChatClient(chatClient); }); builder.Services.AddSingleton(); @@ -41,4 +43,4 @@ public static MauiApp CreateMauiApp() { return builder.Build(); } -} +} \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.MAUIBlazor/wwwroot/index.html b/CS/DevExpress.AI.Samples.MAUIBlazor/wwwroot/index.html index 660776a..d7e6ed8 100644 --- a/CS/DevExpress.AI.Samples.MAUIBlazor/wwwroot/index.html +++ b/CS/DevExpress.AI.Samples.MAUIBlazor/wwwroot/index.html @@ -15,7 +15,12 @@ min-height: 100vh; display: grid; } - .dxbl-chatui-root .dxbl-chatui-submitarea { + + .dxbl-chatui-root { + min-height: 100vh; + } + + .dxbl-chatui-root .dxbl-chatui .dxbl-chatui-submitarea { display: none; } diff --git a/CS/DevExpress.AI.Samples.WPFBlazor/DevExpress.AI.Samples.WPFBlazor.csproj b/CS/DevExpress.AI.Samples.WPFBlazor/DevExpress.AI.Samples.WPFBlazor.csproj index e776c49..9ba2f93 100644 --- a/CS/DevExpress.AI.Samples.WPFBlazor/DevExpress.AI.Samples.WPFBlazor.csproj +++ b/CS/DevExpress.AI.Samples.WPFBlazor/DevExpress.AI.Samples.WPFBlazor.csproj @@ -6,16 +6,18 @@ enable enable true - WPFBlazor + WPFBlazor + true - - - - - + + + + + + diff --git a/CS/DevExpress.AI.Samples.WPFBlazor/MainViewModel.cs b/CS/DevExpress.AI.Samples.WPFBlazor/MainViewModel.cs index 0310c13..392493f 100644 --- a/CS/DevExpress.AI.Samples.WPFBlazor/MainViewModel.cs +++ b/CS/DevExpress.AI.Samples.WPFBlazor/MainViewModel.cs @@ -4,6 +4,8 @@ using Azure.AI.OpenAI; using DevExpress.Mvvm; using DevExpress.AIIntegration; +using System.ClientModel; +using Microsoft.Extensions.AI; namespace DevExpress.AI.Samples.WPFBlazor { class MainViewModel : BindableBase { @@ -37,14 +39,15 @@ void Initialize() string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); + var chatClient = new AzureOpenAIClient( + new Uri(azureOpenAIEndpoint), + new ApiKeyCredential(azureOpenAIKey)).AsChatClient("gpt4o"); + services.AddWpfBlazorWebView(); services.AddDevExpressBlazor(); services.AddDevExpressAI((config) => { - config.RegisterChatClientOpenAIService( - new AzureOpenAIClient( - new Uri(azureOpenAIEndpoint), - new AzureKeyCredential(azureOpenAIKey)), "gpt4o"); + config.RegisterChatClient(chatClient); }); services.AddSingleton(service); @@ -53,9 +56,7 @@ void Initialize() void SendMesssage() { - service.DxChatUI.CurrentMessage = Message; - Message = null; - service.DxChatUI.SendButton?.Click.InvokeAsync(); + service.DxChatUI?.SendMessage(Message, ChatRole.User); } bool CanSendMessage() diff --git a/CS/DevExpress.AI.Samples.WPFBlazor/MainWindow.xaml b/CS/DevExpress.AI.Samples.WPFBlazor/MainWindow.xaml index 744f942..31c3b84 100644 --- a/CS/DevExpress.AI.Samples.WPFBlazor/MainWindow.xaml +++ b/CS/DevExpress.AI.Samples.WPFBlazor/MainWindow.xaml @@ -20,11 +20,11 @@ - + - + { + public class MyDictionary : Dictionary + { } -} +} \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.WPFBlazor/wwwroot/index.html b/CS/DevExpress.AI.Samples.WPFBlazor/wwwroot/index.html index 6e31427..39b8aed 100644 --- a/CS/DevExpress.AI.Samples.WPFBlazor/wwwroot/index.html +++ b/CS/DevExpress.AI.Samples.WPFBlazor/wwwroot/index.html @@ -11,7 +11,11 @@ display: grid; } - .dxbl-chatui-root .dxbl-chatui .dxbl-chartui-submitarea { + .dxbl-chatui-root { + min-height: 100vh; + } + + .dxbl-chatui-root .dxbl-chatui .dxbl-chatui-submitarea { display: none; } @@ -26,8 +30,8 @@ -
Loading...
- +
Loading...
+ \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.WinBlazor/DevExpress.AI.Samples.WinBlazor.csproj b/CS/DevExpress.AI.Samples.WinBlazor/DevExpress.AI.Samples.WinBlazor.csproj index d03178a..81b48aa 100644 --- a/CS/DevExpress.AI.Samples.WinBlazor/DevExpress.AI.Samples.WinBlazor.csproj +++ b/CS/DevExpress.AI.Samples.WinBlazor/DevExpress.AI.Samples.WinBlazor.csproj @@ -7,19 +7,19 @@ true enable DevExpress.AI.Samples.WinBlazor.Program + true - - - - - - - + + + + + + - + <_ContentIncludedByDefault Remove="wwwroot\index.html" /> \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.WinBlazor/Form1.Designer.cs b/CS/DevExpress.AI.Samples.WinBlazor/Form1.Designer.cs index 6786136..4646942 100644 --- a/CS/DevExpress.AI.Samples.WinBlazor/Form1.Designer.cs +++ b/CS/DevExpress.AI.Samples.WinBlazor/Form1.Designer.cs @@ -30,93 +30,17 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.tablePanel1 = new DevExpress.Utils.Layout.TablePanel(); - this.textInput = new DevExpress.XtraEditors.MemoEdit(); - this.simpleButton1 = new DevExpress.XtraEditors.SimpleButton(); - this.blazorWebView1 = new Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView(); - ((System.ComponentModel.ISupportInitialize)(this.tablePanel1)).BeginInit(); - this.tablePanel1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.textInput.Properties)).BeginInit(); this.SuspendLayout(); // - // tablePanel1 - // - this.tablePanel1.Columns.AddRange(new DevExpress.Utils.Layout.TablePanelColumn[] { - new DevExpress.Utils.Layout.TablePanelColumn(DevExpress.Utils.Layout.TablePanelEntityStyle.Relative, 54.53F), - new DevExpress.Utils.Layout.TablePanelColumn(DevExpress.Utils.Layout.TablePanelEntityStyle.Relative, 5.47F)}); - this.tablePanel1.Controls.Add(this.blazorWebView1); - this.tablePanel1.Controls.Add(this.textInput); - this.tablePanel1.Controls.Add(this.simpleButton1); - this.tablePanel1.Dock = System.Windows.Forms.DockStyle.Fill; - this.tablePanel1.Location = new System.Drawing.Point(0, 0); - this.tablePanel1.Name = "tablePanel1"; - this.tablePanel1.Rows.AddRange(new DevExpress.Utils.Layout.TablePanelRow[] { - new DevExpress.Utils.Layout.TablePanelRow(DevExpress.Utils.Layout.TablePanelEntityStyle.Absolute, 760.5F), - new DevExpress.Utils.Layout.TablePanelRow(DevExpress.Utils.Layout.TablePanelEntityStyle.Absolute, 26F)}); - this.tablePanel1.Size = new System.Drawing.Size(1827, 894); - this.tablePanel1.TabIndex = 0; - this.tablePanel1.UseSkinIndents = true; - // - // textInput - // - this.tablePanel1.SetColumn(this.textInput, 0); - this.textInput.Dock = System.Windows.Forms.DockStyle.Fill; - this.textInput.Location = new System.Drawing.Point(26, 785); - this.textInput.Name = "textInput"; - this.textInput.Properties.NullText = "Ask AI Assistant..."; - this.textInput.Properties.NullValuePrompt = "Ask AI Assistant..."; - this.textInput.Properties.UseAdvancedMode = DevExpress.Utils.DefaultBoolean.True; - this.tablePanel1.SetRow(this.textInput, 1); - this.textInput.Size = new System.Drawing.Size(1612, 83); - this.textInput.TabIndex = 1; - this.textInput.KeyDown += textEdit1_KeyDown; - this.textInput.KeyUp += textEdit1_KeyUp; - // - // simpleButton1 - // - this.tablePanel1.SetColumn(this.simpleButton1, 1); - this.simpleButton1.Dock = System.Windows.Forms.DockStyle.Fill; - this.simpleButton1.Location = new System.Drawing.Point(1646, 785); - this.simpleButton1.Name = "simpleButton1"; - this.tablePanel1.SetRow(this.simpleButton1, 1); - this.simpleButton1.Size = new System.Drawing.Size(155, 83); - this.simpleButton1.TabIndex = 0; - this.simpleButton1.Text = "Send"; - this.simpleButton1.Click += SimpleButton1_Click; - // - // blazorWebView1 - // - this.tablePanel1.SetColumn(this.blazorWebView1, 0); - this.tablePanel1.SetColumnSpan(this.blazorWebView1, 2); - this.blazorWebView1.Dock = DockStyle.Fill; - this.blazorWebView1.Location = new Point(12, 12); - this.blazorWebView1.Name = "blazorWebView1"; - this.tablePanel1.SetRow(this.blazorWebView1, 0); - this.blazorWebView1.Size = new Size(776, 386); - this.blazorWebView1.StartPath = "/"; - this.blazorWebView1.TabIndex = 2; - this.blazorWebView1.Text = "blazorWebView1"; - // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1827, 894); - this.Controls.Add(this.tablePanel1); this.Name = "Form1"; this.Text = "Form1"; - ((System.ComponentModel.ISupportInitialize)(this.tablePanel1)).EndInit(); - this.tablePanel1.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.textInput.Properties)).EndInit(); this.ResumeLayout(false); - } - #endregion - - private DevExpress.Utils.Layout.TablePanel tablePanel1; - private DevExpress.XtraEditors.SimpleButton simpleButton1; - private DevExpress.XtraEditors.MemoEdit textInput; - private Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView blazorWebView1; } -} +} \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.WinBlazor/Form1.cs b/CS/DevExpress.AI.Samples.WinBlazor/Form1.cs index 8f91f76..1bf6164 100644 --- a/CS/DevExpress.AI.Samples.WinBlazor/Form1.cs +++ b/CS/DevExpress.AI.Samples.WinBlazor/Form1.cs @@ -1,16 +1,13 @@ -using Azure.AI.OpenAI; -using Azure; -using Microsoft.AspNetCore.Components.WebView.WindowsForms; -using Microsoft.Extensions.DependencyInjection; -using DevExpress.AIIntegration; +using DevExpress.AIIntegration.WinForms.Chat; using DevExpress.Blazor.Internal; using DevExpress.XtraEditors; +using Markdig; +using Microsoft.AspNetCore.Components; namespace DevExpress.AI.Samples.WinBlazor { public partial class Form1 : XtraForm { - DxChatIncapsulationService service = new (); public Form1() { InitializeComponent(); @@ -19,63 +16,20 @@ public Form1() private void InitializeBlazorAIChat() { - var services = new ServiceCollection(); - - string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); - string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); - - services.AddWindowsFormsBlazorWebView(); - services.AddDevExpressBlazor(); - services.AddDevExpressAI((config) => + AIChatControl chat = new AIChatControl() { - config.RegisterChatClientOpenAIService( - new AzureOpenAIClient( - new Uri(azureOpenAIEndpoint), - new AzureKeyCredential(azureOpenAIKey)), "gpt4o"); - }); - services.AddSingleton(service); - blazorWebView1.HostPage = "wwwroot\\index.html"; - blazorWebView1.Services = services.BuildServiceProvider(); - blazorWebView1.RootComponents.Add("#app", - new Dictionary { - { - "UseStreaming", - true - } - }); + Name = "aiChatControl1", + Dock = DockStyle.Fill, + ContentFormat = AIIntegration.Blazor.Chat.ResponseContentFormat.Markdown, + UseStreaming = DevExpress.Utils.DefaultBoolean.True + }; + chat.MarkdownConvert += Chat_MarkdownConvert; + Controls.Add(chat); } - private async void SimpleButton1_Click(object sender, EventArgs e) + private void Chat_MarkdownConvert(object? sender, AIIntegration.Chat.Core.AIChatControlMarkdownConvertEventArgs e) { - service.dxChatUI.CurrentMessage = textInput.Text; - textInput.Text = string.Empty; - simpleButton1.Enabled = false; - await service.dxChatUI.SendButton?.Click.InvokeAsync(); - } - - private async void textEdit1_KeyDown(object sender, KeyEventArgs e) - { - if (e.KeyCode == Keys.Enter) - { - service.dxChatUI.CurrentMessage = textInput.Text; - textInput.Text = string.Empty; - await service.dxChatUI.SendButton?.Click.InvokeAsync(); - } - } - - private void textEdit1_KeyUp(object sender, KeyEventArgs e) - { - simpleButton1.Enabled = textInput.Text.Length > 0; - } - - protected override bool ProcessCmdKey(ref Message msg, Keys keyData) - { - if (keyData == (Keys.Enter & Keys.Control)) - { - simpleButton1.PerformClick(); - return true; - } - return base.ProcessCmdKey(ref msg, keyData); + e.HtmlText = (MarkupString)Markdown.ToHtml(e.MarkdownText); } } -} +} \ No newline at end of file diff --git a/CS/DevExpress.AI.Samples.WinBlazor/ISelfEncapsulationService.cs b/CS/DevExpress.AI.Samples.WinBlazor/ISelfEncapsulationService.cs deleted file mode 100644 index 6df097e..0000000 --- a/CS/DevExpress.AI.Samples.WinBlazor/ISelfEncapsulationService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using DevExpress.AI.Samples.WinBlazor; - -namespace DevExpress.Blazor.Internal { - public interface ISelfEncapsulationService { - void Initialize(WinChatUIWrapper dxChatUI); - } - - class DxChatIncapsulationService : ISelfEncapsulationService { - public WinChatUIWrapper? dxChatUI; - public void Initialize(WinChatUIWrapper dxChatUI) { - this.dxChatUI = dxChatUI; - } - } -} diff --git a/CS/DevExpress.AI.Samples.WinBlazor/Program.cs b/CS/DevExpress.AI.Samples.WinBlazor/Program.cs index fd7666d..98d244d 100644 --- a/CS/DevExpress.AI.Samples.WinBlazor/Program.cs +++ b/CS/DevExpress.AI.Samples.WinBlazor/Program.cs @@ -1,5 +1,10 @@ +using DevExpress.AIIntegration; +using Microsoft.Extensions.AI; + namespace DevExpress.AI.Samples.WinBlazor { static class Program { + static string AzureOpenAIEndpoint { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); } } + static string AzureOpenAIKey { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); } } /// /// The main entry point for the application. /// @@ -8,6 +13,10 @@ static void Main() { // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); + IChatClient asChatClient = new Azure.AI.OpenAI.AzureOpenAIClient(new Uri(AzureOpenAIEndpoint), + new System.ClientModel.ApiKeyCredential(AzureOpenAIKey)) + .AsChatClient("GPT4o"); + AIExtensionsContainerDesktop.Default.RegisterChatClient(asChatClient); Application.Run(new Form1()); } } diff --git a/CS/DevExpress.AI.Samples.WinBlazor/WinChatUIWrapper.cs b/CS/DevExpress.AI.Samples.WinBlazor/WinChatUIWrapper.cs deleted file mode 100644 index bcf622d..0000000 --- a/CS/DevExpress.AI.Samples.WinBlazor/WinChatUIWrapper.cs +++ /dev/null @@ -1,20 +0,0 @@ -using DevExpress.AIIntegration.Blazor.Chat; -using DevExpress.Blazor; -using DevExpress.Blazor.Internal; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; - -namespace DevExpress.AI.Samples.WinBlazor { - public class WinChatUIWrapper : DxAIChat { - [Inject] ISelfEncapsulationService SelfIncapsulationService { get; set; } = default!; - protected override void OnInitialized() { - SelfIncapsulationService.Initialize(this); - base.OnInitialized(); - } - - protected override void BuildRenderTree(RenderTreeBuilder builder) { - DxResourceManager.RegisterScripts()(builder); - base.BuildRenderTree(builder); - } - } -} diff --git a/CS/DevExpress.AI.Samples.WinBlazor/wwwroot/index.html b/CS/DevExpress.AI.Samples.WinBlazor/wwwroot/index.html deleted file mode 100644 index 073819b..0000000 --- a/CS/DevExpress.AI.Samples.WinBlazor/wwwroot/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - -
Loading...
- - - - \ No newline at end of file diff --git a/README.md b/README.md index 16519e8..0ab6902 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -![](https://img.shields.io/endpoint?url=https://codecentral.devexpress.com/api/v1/VersionRange/851207927/24.2.1%2B) [![](https://img.shields.io/badge/Open_in_DevExpress_Support_Center-FF7200?style=flat-square&logo=DevExpress&logoColor=white)](https://supportcenter.devexpress.com/ticket/details/T1251539) [![](https://img.shields.io/badge/📖_How_to_use_DevExpress_Examples-e9f6fc?style=flat-square)](https://docs.devexpress.com/GeneralInformation/403183) [![](https://img.shields.io/badge/💬_Leave_Feedback-feecdd?style=flat-square)](#does-this-example-address-your-development-requirementsobjectives) @@ -7,7 +6,7 @@ # Blazor AI Chat - How to add the DevExpress Blazor AI Chat component to your next Blazor, MAUI, WPF, and WinForms application -The DevExpress Blazor AI Chat component (DxAIChat) allows you to incorporate AI-powered interactions into any Blazor/MAUI/WPF/WinForms application. Our AI Chat component ships with a variety of high impact features, including: +The DevExpress Blazor AI Chat component ([DxAIChat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat?v=24.2)) allows you to incorporate AI-powered interactions into any Blazor/MAUI/WPF/WinForms application. Our AI Chat component ships with a variety of high impact features, including: * [Customizable message appearance and empty message area](#customize-message-appearance-and-empty-message-area) * [Text or markdown response](#text-or-markdown-response) @@ -16,24 +15,33 @@ The DevExpress Blazor AI Chat component (DxAIChat) allows you to incorporate AI- ## Implementation Details -This example adds a `DxAIChat` to a Blazor application, customizes its settings, and integrates it into WinForms, WPF, and .NET MAUI applications. +This example adds a [DxAIChat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat?v=24.2) to a Blazor application, customizes its settings, and integrates it into WinForms, WPF, and .NET MAUI applications. ### Register AI Service +> [!NOTE] +> DevExpress AI-powered extensions follow the "bring your own key" principle. DevExpress does not offer a REST API and does not ship any built-in LLMs/SLMs. You need an active Azure/Open AI subscription to obtain the REST API endpoint, key, and model deployment name. These variables must be specified at application startup to register AI clients and enable DevExpress AI-powered Extensions in your application. + Add the following code to the _Program.cs_ file to register the AI Chat service in your application: ```cs +using Azure.AI.OpenAI; using DevExpress.AIIntegration; +using Microsoft.Extensions.AI; +using System.ClientModel; string azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); string azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); +string deploymentName = string.Empty; ... -builder.Services.AddDevExpressAI((config) => { - var client = new AzureOpenAIClient( - new Uri(azureOpenAIEndpoint), - new AzureKeyCredential(azureOpenAIKey)); - config.RegisterChatClientOpenAIService(client, "gpt4o"); -}); +var azureClient = new AzureOpenAIClient( + new Uri(azureOpenAIEndpoint), + new ApiKeyCredential(azureOpenAIKey)); + +builder.Services.AddDevExpressBlazor(); +builder.Services.AddChatClient(cfg => + cfg.Use(azureClient.AsChatClient((deploymentName))) +); ``` File to review: [Program.cs](./CS/DevExpress.AI.Samples.Blazor/Program.cs) @@ -63,11 +71,11 @@ File to review: [Chat.razor](./CS/DevExpress.AI.Samples.Blazor/Components/Pages/ ### Customize message appearance and empty message area -`DxAIChat` component includes the following message customization properties: +[DxAIChat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat?v=24.2) component includes the following message customization properties: -* `MessageTemplate` - specifies the template used for message bubbles. -* `MessageContentTemplate` - specifies the template used for message bubble content. -* `EmptyMessageAreaTemplate` - specifies the template used for the empty message area. +* [MessageTemplate](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.MessageTemplate?v=24.2) - specifies the template used for message bubbles. +* [MessageContentTemplate](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.MessageContentTemplate?v=24.2) - specifies the template used for message bubble content. +* [EmptyMessageAreaTemplate](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.EmptyMessageAreaTemplate?v=24.2) - specifies the template used for the empty message area. ```razor @@ -96,7 +104,7 @@ File to review: [Chat-CustomMessage.razor](./CS/DevExpress.AI.Samples.Blazor/Com The AI service uses plain text as the default response format. -To display rich formatted messages, set the `RenderMode` property to `Markdown`. Use a markdown processor to convert response content to HTML code. +To display rich formatted messages, set the [ResponseContentFormat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.ResponseContentFormat?v=24.2) property to `Markdown`. Use a markdown processor to convert response content to HTML code. ```razor @using Markdig; @@ -118,16 +126,15 @@ To display rich formatted messages, set the `RenderMode` property to `Markdown`. ### Manual message processing -When a user sends a message to the chat, the `MessageSent` event fires. Handle the event to manually process this action. -You can use the `Content` event argument to access user input and call the `SendMessage` method to send another message to the chat. +When a user sends a message to the chat, the [MessageSent](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.MessageSent?v=24.2) event fires. Handle the event to manually process this action. +You can use the [Content](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.MessageSentEventArgs.Content?v=24.2) event argument to access user input and call the [SendMessage](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.SendMessage(System.String-Microsoft.Extensions.AI.ChatRole)?v=24.2) method to send another message to the chat. ```razor @code { - void MessageSent(MessageSentEventArgs args) { - var message = new Message(MessageRole.Assistant, $"Processed: {args.Content}"); - args.SendMessage(message); + async Task MessageSent(MessageSentEventArgs args) { + await args.Chat.SendMessage($"Processed: {args.Content}", Microsoft.Extensions.AI.ChatRole.Assistant); } } ``` @@ -137,7 +144,7 @@ File to review: [Chat-MessageSent.razor](./CS/DevExpress.AI.Samples.Blazor/Compo ### Streaming response -After a user sends a request, the AI client generates and sends the entire response back. This operation may be time consuming. To make the chat appear more responsive, set the `UseStreaming` property to `true`. In this instance, the AI client transmits parts of the response as it becomes available and the chat component adds these parts to the display message. +After a user sends a request, the AI client generates and sends the entire response back. This operation may be time consuming. To make the chat appear more responsive, set the [UseStreaming](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.UseStreaming?v=24.2) property to `true`. In this instance, the AI client transmits parts of the response as it becomes available and the chat component adds these parts to the display message. ```razor @@ -147,14 +154,14 @@ File to review: [Chat-Streaming.razor](./CS/DevExpress.AI.Samples.Blazor/Compone ### Compatibility with OpenAI assistants -The DevExpress AI Chat (`DxAIChat`) component supports [OpenAI Assistants](https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/announcing-azure-openai-service-assistants-public-preview/ba-p/4143217). This allows you to specify a model and supply supplementary documents (external knowledge). OpenAI parses these documents and searches through them to retrieve relevant content to answer user queries. +The DevExpress AI Chat ([DxAIChat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat?v=24.2)) component supports [OpenAI Assistants](https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/announcing-azure-openai-service-assistants-public-preview/ba-p/4143217). This allows you to specify a model and supply supplementary documents (external knowledge). OpenAI parses these documents and searches through them to retrieve relevant content to answer user queries. Add the following code to the _Program.cs_ file to register AI Assistant service in the application: ```cs builder.Services.AddDevExpressAI((config) => { - ... - config.RegisterOpenAIAssistants(client, "gpt4o"); + //Reference the DevExpress.AIIntegration.OpenAI NuGet package to use Open AI Asisstants + config.RegisterOpenAIAssistants(azureClient, "gpt4o"); }); ``` @@ -164,7 +171,7 @@ Include a supplementary document in the project file as an `EmbeddedResource`: ``` -Handle the `Initialized` event and call the `UseAssistantAsync` method to supply a file to the Open AI Assistant. +Handle the [Initialized](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat.Initialized?v=24.2) event and call the [SetupAssistantAsync](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.IAIChat.SetupAssistantAsync.overloads?v=24.2) method to supply a file to the Open AI Assistant. ```razor @@ -174,7 +181,7 @@ Handle the `Initialized` event and call the `UseAssistantAsync` method to supply const string prompt = "..."; async Task Initialized(IAIChat chat) { - await chat.UseAssistantAsync(new OpenAIAssistantOptions( + await chat.SetupAssistantAsync(new OpenAIAssistantOptions( $"{Guid.NewGuid().ToString("N")}.pdf", Assembly.GetExecutingAssembly().GetManifestResourceStream(DocumentResourceName), prompt) @@ -187,15 +194,19 @@ File to review: [Chat-Assistant.razor](./CS/DevExpress.AI.Samples.Blazor/Compone ### Integrate AI Chat into WinForms, WPF and .NET MAUI Apps -Thanks to both Blazor Hybrid technology and the BlazorWebView component, you can integrate DevExpress AI Chat (`DxAIChat`) into your next great WinForms, WPF, and .NET MAUI application. +Thanks to both Blazor Hybrid technology and the BlazorWebView component, you can integrate DevExpress AI Chat ([DxAIChat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat?v=24.2)) into your next great WinForms, WPF, and .NET MAUI application. Keys to implementation are as follows: -* The `ISelfEncapsulationService` interface allows you to work directly with the `DxAIChat` component instance/properties from your desktop or mobile app. -* Built-in `DxAIChat` wrappers initialize required Blazor Theme scripts. -* Custom CSS classes hide the built-in input field and the Send button (see _index.htm_). +* The `ISelfEncapsulationService` interface allows you to work directly with the [DxAIChat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat?v=24.2) component instance/properties from your desktop or mobile app. +* Built-in [DxAIChat](https://docs.devexpress.com/Blazor/DevExpress.AIIntegration.Blazor.Chat.DxAIChat?v=24.2) wrappers initialize required Blazor Theme scripts. +* Custom CSS classes hide the built-in input field and the Send button (see _index.html_). + +Folders to review: [DevExpress.AI.Samples.MAUIBlazor](./CS/DevExpress.AI.Samples.MAUIBlazor/), [DevExpress.AI.Samples.WPFBlazor](./CS/DevExpress.AI.Samples.WPFBlazor/) + +For WinForms apps, use the built-in `AIChatControl` component. Refer to the following help topic to learn more about the integration steps: [AI Chat Control Documentation](https://docs.devexpress.com/WindowsForms/405218/ai-powered-extensions/ai-chat-control) -Folders to review: [DevExpress.AI.Samples.MAUIBlazor](./CS/DevExpress.AI.Samples.MAUIBlazor/), [DevExpress.AI.Samples.WinBlazor](./CS/DevExpress.AI.Samples.WinBlazor/), [DevExpress.AI.Samples.WPFBlazor](./CS/DevExpress.AI.Samples.WPFBlazor/) +Folders to review: [DevExpress.AI.Samples.WinBlazor](./CS/DevExpress.AI.Samples.WinBlazor/) ## Files to Review