Skip to content

Commit b7a01bb

Browse files
KseniyaKuzminaDevExpressExampleBot
andauthored
update wpf project (#4)
* update wpf project * README auto update [skip ci] * Update README.md --------- Co-authored-by: DevExpressExampleBot <fake@test.test>
1 parent 8b0b305 commit b7a01bb

File tree

15 files changed

+394
-243
lines changed

15 files changed

+394
-243
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
</configuration>
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1-
<Application x:Class="DevExpress.AI.Samples.WPFBlazor.App"
1+
<Application x:Class="WPF_AIChatControl.App"
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4-
xmlns:local="clr-namespace:DevExpress.AI.Samples.WPFBlazor"
4+
xmlns:local="clr-namespace:WPF_AIChatControl"
5+
xmlns:dxi="http://schemas.devexpress.com/winfx/2008/xaml/core/internal"
56
StartupUri="MainWindow.xaml">
67
<Application.Resources>
7-
8+
<Style TargetType="local:AIChatControl">
9+
<Setter Property="Background" Value="{dxi:ThemeResource ThemeKey='Brush.WindowBackground'}"/>
10+
<Setter Property="Foreground" Value="{dxi:ThemeResource ThemeKey='Brush.Foreground.Primary'}"/>
11+
<Setter Property="ControlBackground" Value="{dxi:ThemeResource ThemeKey='Brush.Editor.Background'}"/>
12+
<Setter Property="ItemBackground" Value="{dxi:ThemeResource ThemeKey='Brush.ListItem.SelectionAlt'}"/>
13+
<Setter Property="Template">
14+
<Setter.Value>
15+
<ControlTemplate>
16+
<local:ChatBlazorWebView x:Name="PART_ChatWebView"
17+
Margin="2"/>
18+
</ControlTemplate>
19+
</Setter.Value>
20+
</Setter>
21+
</Style>
822
</Application.Resources>
923
</Application>
Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
1-
using System.Configuration;
2-
using System.Data;
1+
using System;
32
using System.Windows;
3+
using DevExpress.AIIntegration;
4+
using Azure.AI.OpenAI;
5+
using DevExpress.Data.Utils;
6+
using DevExpress.Xpf.Core;
7+
using Microsoft.Extensions.AI;
48

5-
namespace DevExpress.AI.Samples.WPFBlazor
6-
{
9+
namespace WPF_AIChatControl {
710
/// <summary>
811
/// Interaction logic for App.xaml
912
/// </summary>
10-
public partial class App : System.Windows.Application
11-
{
13+
public partial class App : Application {
14+
static App() {
15+
CompatibilitySettings.UseLightweightThemes = true;
16+
ApplicationThemeHelper.ApplicationThemeName = Theme.Win11Light.Name;
17+
18+
SetupAzureOpenAI();
19+
}
20+
static void SetupAzureOpenAI() {
21+
22+
string azureOpenAIEndpoint = SafeEnvironment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
23+
string azureOpenAIKey = SafeEnvironment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
24+
string deployment = "gpt-4o-mini";
25+
26+
var openAIClient = new AzureOpenAIClient(
27+
new Uri(azureOpenAIEndpoint),
28+
new System.ClientModel.ApiKeyCredential(azureOpenAIKey)
29+
);
30+
var container = AIExtensionsContainerDesktop.Default;
31+
container.RegisterChatClient(openAIClient.AsChatClient(deployment));
32+
container.RegisterOpenAIAssistants(openAIClient, deployment);
33+
}
1234
}
1335

1436
}
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.ComponentModel;
5+
using DevExpress.Blazor.Internal;
6+
using Microsoft.AspNetCore.Components.WebView.Wpf;
7+
using DevExpress.AIIntegration.Blazor.Chat;
8+
using DevExpress.AIIntegration.Services.Assistant;
9+
using Microsoft.AspNetCore.Components;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using DevExpress.AIIntegration.Blazor.Chat.WebView;
12+
using DevExpress.Utils;
13+
using System.Collections.Generic;
14+
using System.Drawing;
15+
using DevExpress.AIIntegration;
16+
using Microsoft.Extensions.AI;
17+
using System.Threading.Tasks;
18+
using DevExpress.Xpf.Printing.Native;
19+
20+
namespace WPF_AIChatControl {
21+
public class AIChatControl : Control {
22+
23+
public static readonly DependencyProperty UseStreamingProperty;
24+
public static readonly DependencyProperty ContentFormatProperty;
25+
public static readonly DependencyProperty EmptyStateTextProperty;
26+
public static readonly DependencyProperty TemperatureProperty;
27+
public static readonly DependencyProperty MaxTokensProperty;
28+
public static readonly DependencyProperty FrequencyPenaltyProperty;
29+
public static readonly DependencyProperty ControlBackgroundProperty;
30+
public static readonly DependencyProperty ItemBackgroundProperty;
31+
32+
static AIChatControl() {
33+
var ownerType = typeof(AIChatControl);
34+
UseStreamingProperty = DependencyProperty.Register(nameof(UseStreaming), typeof(bool), ownerType,
35+
new PropertyMetadata(false, (d, e) => ((AIChatControl)d).OnUseStreamingChanged()));
36+
ContentFormatProperty = DependencyProperty.Register(nameof(ContentFormat), typeof(ResponseContentFormat), ownerType,
37+
new PropertyMetadata(ResponseContentFormat.PlainText, (d, e) => ((AIChatControl)d).OnContentFormatChanged()));
38+
EmptyStateTextProperty = DependencyProperty.Register(nameof(EmptyStateText), typeof(string), ownerType,
39+
new PropertyMetadata(string.Empty, (d, e) => ((AIChatControl)d).OnEmptyStateTextChanged()));
40+
TemperatureProperty = DependencyProperty.Register(nameof(Temperature), typeof(float?), ownerType,
41+
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnTemperatureChanged()));
42+
MaxTokensProperty = DependencyProperty.Register(nameof(MaxTokens), typeof(int?), ownerType,
43+
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnMaxTokensChanged()));
44+
FrequencyPenaltyProperty = DependencyProperty.Register(nameof(FrequencyPenalty), typeof(float?), ownerType,
45+
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnFrequencyPenaltyChanged()));
46+
ControlBackgroundProperty = DependencyProperty.Register(nameof(ControlBackground), typeof(System.Windows.Media.Brush), ownerType,
47+
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnControlBackgroundChanged()));
48+
ItemBackgroundProperty = DependencyProperty.Register(nameof(ItemBackground), typeof(System.Windows.Media.Brush), ownerType,
49+
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnItemBackgroundChanged()));
50+
}
51+
52+
DxChatIncapsulationService incapsulationService;
53+
RootComponent blazorChatComponent;
54+
ChatBlazorWebView chatWebView;
55+
56+
public bool UseStreaming {
57+
get => (bool)GetValue(UseStreamingProperty);
58+
set => SetValue(UseStreamingProperty, value);
59+
}
60+
public ResponseContentFormat ContentFormat {
61+
get => (ResponseContentFormat)GetValue(ContentFormatProperty);
62+
set => SetValue(ContentFormatProperty, value);
63+
}
64+
public string EmptyStateText {
65+
get => (string)GetValue(EmptyStateTextProperty);
66+
set => SetValue(EmptyStateTextProperty, value);
67+
}
68+
public float? Temperature {
69+
get => (float?)GetValue(TemperatureProperty);
70+
set => SetValue(TemperatureProperty, value);
71+
}
72+
public int? MaxTokens {
73+
get => (int?)GetValue(MaxTokensProperty);
74+
set => SetValue(MaxTokensProperty, value);
75+
}
76+
public float? FrequencyPenalty {
77+
get => (float?)GetValue(FrequencyPenaltyProperty);
78+
set => SetValue(FrequencyPenaltyProperty, value);
79+
}
80+
public System.Windows.Media.Brush ControlBackground {
81+
get => (System.Windows.Media.Brush)GetValue(ControlBackgroundProperty);
82+
set => SetValue(ControlBackgroundProperty, value);
83+
}
84+
public System.Windows.Media.Brush ItemBackground {
85+
get => (System.Windows.Media.Brush)GetValue(ItemBackgroundProperty);
86+
set => SetValue(ItemBackgroundProperty, value);
87+
}
88+
89+
IChatUIWrapper Chat => incapsulationService?.DxChatUI;
90+
91+
EventHandler<AIChatControlMarkdownConvertEventArgs> markdownConvert;
92+
public event EventHandler<AIChatControlMarkdownConvertEventArgs> MarkdownConvert {
93+
add => markdownConvert += value;
94+
remove => markdownConvert -= value;
95+
}
96+
EventHandler<AIChatControlMessageSentEventArgs> messageSent;
97+
public event EventHandler<AIChatControlMessageSentEventArgs> MessageSent {
98+
add {
99+
if(messageSent == null && Chat != null)
100+
Chat.SetMessageSentCallback(RaiseMessageSent);
101+
messageSent += value;
102+
}
103+
remove {
104+
messageSent -= value;
105+
if(messageSent == null && Chat != null)
106+
Chat.SetMessageSentCallback(null);
107+
}
108+
}
109+
110+
public async Task SendMessage(string text, ChatRole role) {
111+
if (Chat != null) {
112+
await Chat.SendMessage(text, role);
113+
Chat.Update();
114+
}
115+
}
116+
public IEnumerable<BlazorChatMessage> SaveMessages() {
117+
return Chat?.SaveMessages() ?? [];
118+
}
119+
public void LoadMessages(IEnumerable<BlazorChatMessage> messages) {
120+
if (Chat != null) {
121+
Chat.LoadMessages(messages);
122+
Chat.Update();
123+
}
124+
}
125+
public override void OnApplyTemplate() {
126+
base.OnApplyTemplate();
127+
this.chatWebView = GetTemplateChild("PART_ChatWebView") as ChatBlazorWebView;
128+
ConfigureBlazorWebView();
129+
}
130+
131+
void RaiseMessageSent(MessageSentEventArgs args) {
132+
messageSent?.Invoke(this, new AIChatControlMessageSentEventArgs(Chat, args.Content));
133+
}
134+
MarkupString RaiseMarkdownConvert(string text) {
135+
if(markdownConvert != null) {
136+
var e = new AIChatControlMarkdownConvertEventArgs(text);
137+
markdownConvert(this, e);
138+
return e.HtmlText ?? new MarkupString();
139+
}
140+
return new MarkupString();
141+
}
142+
void OnUseStreamingChanged() {
143+
if(Chat != null) {
144+
Chat.UseStreaming = UseStreaming;
145+
Chat.Update();
146+
}
147+
}
148+
void OnContentFormatChanged() {
149+
if(Chat != null) {
150+
Chat.ResponseContentFormat = ContentFormat;
151+
Chat.SetMarkdownConvertCallback(ContentFormat == ResponseContentFormat.Markdown ? RaiseMarkdownConvert : null);
152+
Chat.Update();
153+
}
154+
}
155+
void OnEmptyStateTextChanged() {
156+
if(Chat != null) {
157+
Chat.SetEmptyStateText(EmptyStateText);
158+
Chat.Update();
159+
}
160+
}
161+
void OnTemperatureChanged() {
162+
if(Chat != null)
163+
Chat.Temperature = Temperature;
164+
}
165+
void OnMaxTokensChanged() {
166+
if(Chat != null)
167+
Chat.MaxTokens = MaxTokens;
168+
}
169+
void OnFrequencyPenaltyChanged() {
170+
if(Chat != null)
171+
Chat.FrequencyPenalty = FrequencyPenalty;
172+
}
173+
void OnItemBackgroundChanged() {
174+
if(Chat == null)
175+
return;
176+
Chat.Colors[ChatUIColor.UserMessageBackground] = ItemBackground.ToColor();
177+
}
178+
void OnControlBackgroundChanged() {
179+
if(Chat == null)
180+
return;
181+
var controlBackground = ControlBackground.ToColor();
182+
Chat.Colors[ChatUIColor.SubmitAreaBackground] = controlBackground;
183+
Chat.Colors[ChatUIColor.InputBackground] = controlBackground;
184+
Chat.Colors[ChatUIColor.AssistantMessageBackground] = controlBackground;
185+
Chat.Colors[ChatUIColor.ButtonNormalBackground] = controlBackground;
186+
Chat.Colors[ChatUIColor.ButtonDisabledBackground] = controlBackground;
187+
188+
}
189+
void OnBackgroundChanged() {
190+
if(Chat == null)
191+
return;
192+
var background = Background.ToColor();
193+
Chat.Colors[ChatUIColor.Background] = background;
194+
Chat.Colors[ChatUIColor.ButtonHoverBackground] = background;
195+
196+
}
197+
void OnForegroundChanged() {
198+
if(Chat == null)
199+
return;
200+
var foreground = Foreground.ToColor();
201+
Chat.Colors[ChatUIColor.EmptyForeground] = foreground;
202+
Chat.Colors[ChatUIColor.ScrollViewer] = foreground;
203+
Chat.Colors[ChatUIColor.InputForeground] = foreground;
204+
Chat.Colors[ChatUIColor.InputBorder] = Color.FromArgb((int)(255 * 0.25), foreground);
205+
Chat.Colors[ChatUIColor.InputFocusShadow] = Color.FromArgb((int)(255 * 0.1), foreground);
206+
Chat.Colors[ChatUIColor.MessageBorder] = Color.FromArgb((int)(255 * 0.25), foreground);
207+
Chat.Colors[ChatUIColor.UserMessageForeground] = foreground;
208+
Chat.Colors[ChatUIColor.AssistantMessageForeground] = foreground;
209+
210+
}
211+
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
212+
base.OnPropertyChanged(e);
213+
if(e.Property == BackgroundProperty)
214+
OnBackgroundChanged();
215+
if(e.Property == ForegroundProperty)
216+
OnForegroundChanged();
217+
}
218+
void ConfigureBlazorWebView() {
219+
incapsulationService = new DxChatIncapsulationService();
220+
chatWebView.HostPage = StaticResourceIdentifiers.HostPageFilePath;
221+
chatWebView.Services = GetServiceProvider(incapsulationService);
222+
chatWebView.RootComponents.Add(new RootComponent() {
223+
Selector = StaticResourceIdentifiers.AppDivId,
224+
ComponentType = typeof(ChatUIWrapper),
225+
Parameters = new Dictionary<string, object>() {
226+
{ nameof(DxAIChat.Initialized), new EventCallback<IAIChat>(null, OnChatInitialized)}
227+
}
228+
});
229+
blazorChatComponent = chatWebView.RootComponents[0];
230+
}
231+
ServiceProvider GetServiceProvider(DxChatIncapsulationService incapsulationService) {
232+
var chatClientAIService = AIExtensionsContainerDesktop.Default.GetService<IChatClient>();
233+
if(chatClientAIService == null)
234+
throw new InvalidOperationException("There is no registered service of type Microsoft.Extensions.AI.IChatClient");
235+
var aiAssistantFactory = AIExtensionsContainerDesktop.Default.GetService<IAIAssistantFactory>();
236+
return ServiceProviderBuildHelper.BuildServiceProvider(incapsulationService,
237+
s => s.AddWpfBlazorWebView(),
238+
chatClientAIService, aiAssistantFactory);
239+
}
240+
void OnChatInitialized(IAIChat chat) {
241+
var chatWrapper = chat as IChatUIWrapper;
242+
if(chatWrapper == null)
243+
return;
244+
chatWrapper.UseStreaming = UseStreaming;
245+
chatWrapper.ResponseContentFormat = ContentFormat;
246+
chatWrapper.SetEmptyStateText(EmptyStateText);
247+
chatWrapper.Temperature = Temperature;
248+
chatWrapper.FrequencyPenalty = FrequencyPenalty;
249+
chatWrapper.MaxTokens = MaxTokens;
250+
if(messageSent != null)
251+
chatWrapper.SetMessageSentCallback(RaiseMessageSent);
252+
if(ContentFormat == ResponseContentFormat.Markdown)
253+
chatWrapper.SetMarkdownConvertCallback(RaiseMarkdownConvert);
254+
OnBackgroundChanged();
255+
OnForegroundChanged();
256+
OnControlBackgroundChanged();
257+
OnItemBackgroundChanged();
258+
}
259+
}
260+
static class ColorExtensions {
261+
public static Color ToColor(this System.Windows.Media.Brush brush) {
262+
return (brush as System.Windows.Media.SolidColorBrush)?.Color.ToColor() ?? Color.Black;
263+
}
264+
}
265+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using DevExpress.AIIntegration.Blazor.Chat.WebView;
2+
using Microsoft.AspNetCore.Components.WebView.Wpf;
3+
using Microsoft.Extensions.FileProviders;
4+
5+
namespace WPF_AIChatControl {
6+
internal class ChatBlazorWebView : BlazorWebView {
7+
public override IFileProvider CreateFileProvider(string contentRootDir) {
8+
return new EmbeddedFileProvider(
9+
typeof(IChatUIWrapper).Assembly,
10+
typeof(ChatUIWrapper).Namespace);
11+
}
12+
}
13+
}

CS/DevExpress.AI.Samples.WPFBlazor/DevExpress.AI.Samples.WPFBlazor.csproj

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)