Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update wpf project #4

Merged
merged 3 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CS/DevExpress.AI.Samples.WPFBlazor/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
</configuration>
20 changes: 17 additions & 3 deletions CS/DevExpress.AI.Samples.WPFBlazor/App.xaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
<Application x:Class="DevExpress.AI.Samples.WPFBlazor.App"
<Application x:Class="WPF_AIChatControl.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DevExpress.AI.Samples.WPFBlazor"
xmlns:local="clr-namespace:WPF_AIChatControl"
xmlns:dxi="http://schemas.devexpress.com/winfx/2008/xaml/core/internal"
StartupUri="MainWindow.xaml">
<Application.Resources>

<Style TargetType="local:AIChatControl">
<Setter Property="Background" Value="{dxi:ThemeResource ThemeKey='Brush.WindowBackground'}"/>
<Setter Property="Foreground" Value="{dxi:ThemeResource ThemeKey='Brush.Foreground.Primary'}"/>
<Setter Property="ControlBackground" Value="{dxi:ThemeResource ThemeKey='Brush.Editor.Background'}"/>
<Setter Property="ItemBackground" Value="{dxi:ThemeResource ThemeKey='Brush.ListItem.SelectionAlt'}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<local:ChatBlazorWebView x:Name="PART_ChatWebView"
Margin="2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>
34 changes: 28 additions & 6 deletions CS/DevExpress.AI.Samples.WPFBlazor/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
using System.Configuration;
using System.Data;
using System;
using System.Windows;
using DevExpress.AIIntegration;
using Azure.AI.OpenAI;
using DevExpress.Data.Utils;
using DevExpress.Xpf.Core;
using Microsoft.Extensions.AI;

namespace DevExpress.AI.Samples.WPFBlazor
{
namespace WPF_AIChatControl {
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : System.Windows.Application
{
public partial class App : Application {
static App() {
CompatibilitySettings.UseLightweightThemes = true;
ApplicationThemeHelper.ApplicationThemeName = Theme.Win11Light.Name;

SetupAzureOpenAI();
}
static void SetupAzureOpenAI() {

string azureOpenAIEndpoint = SafeEnvironment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
string azureOpenAIKey = SafeEnvironment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
string deployment = "gpt-4o-mini";

var openAIClient = new AzureOpenAIClient(
new Uri(azureOpenAIEndpoint),
new System.ClientModel.ApiKeyCredential(azureOpenAIKey)
);
var container = AIExtensionsContainerDesktop.Default;
container.RegisterChatClient(openAIClient.AsChatClient(deployment));
container.RegisterOpenAIAssistants(openAIClient, deployment);
}
}

}
265 changes: 265 additions & 0 deletions CS/DevExpress.AI.Samples.WPFBlazor/Controls/AIChatControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using DevExpress.Blazor.Internal;
using Microsoft.AspNetCore.Components.WebView.Wpf;
using DevExpress.AIIntegration.Blazor.Chat;
using DevExpress.AIIntegration.Services.Assistant;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using DevExpress.AIIntegration.Blazor.Chat.WebView;
using DevExpress.Utils;
using System.Collections.Generic;
using System.Drawing;
using DevExpress.AIIntegration;
using Microsoft.Extensions.AI;
using System.Threading.Tasks;
using DevExpress.Xpf.Printing.Native;

namespace WPF_AIChatControl {
public class AIChatControl : Control {

public static readonly DependencyProperty UseStreamingProperty;
public static readonly DependencyProperty ContentFormatProperty;
public static readonly DependencyProperty EmptyStateTextProperty;
public static readonly DependencyProperty TemperatureProperty;
public static readonly DependencyProperty MaxTokensProperty;
public static readonly DependencyProperty FrequencyPenaltyProperty;
public static readonly DependencyProperty ControlBackgroundProperty;
public static readonly DependencyProperty ItemBackgroundProperty;

static AIChatControl() {
var ownerType = typeof(AIChatControl);
UseStreamingProperty = DependencyProperty.Register(nameof(UseStreaming), typeof(bool), ownerType,
new PropertyMetadata(false, (d, e) => ((AIChatControl)d).OnUseStreamingChanged()));
ContentFormatProperty = DependencyProperty.Register(nameof(ContentFormat), typeof(ResponseContentFormat), ownerType,
new PropertyMetadata(ResponseContentFormat.PlainText, (d, e) => ((AIChatControl)d).OnContentFormatChanged()));
EmptyStateTextProperty = DependencyProperty.Register(nameof(EmptyStateText), typeof(string), ownerType,
new PropertyMetadata(string.Empty, (d, e) => ((AIChatControl)d).OnEmptyStateTextChanged()));
TemperatureProperty = DependencyProperty.Register(nameof(Temperature), typeof(float?), ownerType,
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnTemperatureChanged()));
MaxTokensProperty = DependencyProperty.Register(nameof(MaxTokens), typeof(int?), ownerType,
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnMaxTokensChanged()));
FrequencyPenaltyProperty = DependencyProperty.Register(nameof(FrequencyPenalty), typeof(float?), ownerType,
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnFrequencyPenaltyChanged()));
ControlBackgroundProperty = DependencyProperty.Register(nameof(ControlBackground), typeof(System.Windows.Media.Brush), ownerType,
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnControlBackgroundChanged()));
ItemBackgroundProperty = DependencyProperty.Register(nameof(ItemBackground), typeof(System.Windows.Media.Brush), ownerType,
new PropertyMetadata(null, (d, e) => ((AIChatControl)d).OnItemBackgroundChanged()));
}

DxChatIncapsulationService incapsulationService;
RootComponent blazorChatComponent;
ChatBlazorWebView chatWebView;

public bool UseStreaming {
get => (bool)GetValue(UseStreamingProperty);
set => SetValue(UseStreamingProperty, value);
}
public ResponseContentFormat ContentFormat {
get => (ResponseContentFormat)GetValue(ContentFormatProperty);
set => SetValue(ContentFormatProperty, value);
}
public string EmptyStateText {
get => (string)GetValue(EmptyStateTextProperty);
set => SetValue(EmptyStateTextProperty, value);
}
public float? Temperature {
get => (float?)GetValue(TemperatureProperty);
set => SetValue(TemperatureProperty, value);
}
public int? MaxTokens {
get => (int?)GetValue(MaxTokensProperty);
set => SetValue(MaxTokensProperty, value);
}
public float? FrequencyPenalty {
get => (float?)GetValue(FrequencyPenaltyProperty);
set => SetValue(FrequencyPenaltyProperty, value);
}
public System.Windows.Media.Brush ControlBackground {
get => (System.Windows.Media.Brush)GetValue(ControlBackgroundProperty);
set => SetValue(ControlBackgroundProperty, value);
}
public System.Windows.Media.Brush ItemBackground {
get => (System.Windows.Media.Brush)GetValue(ItemBackgroundProperty);
set => SetValue(ItemBackgroundProperty, value);
}

IChatUIWrapper Chat => incapsulationService?.DxChatUI;

EventHandler<AIChatControlMarkdownConvertEventArgs> markdownConvert;
public event EventHandler<AIChatControlMarkdownConvertEventArgs> MarkdownConvert {
add => markdownConvert += value;
remove => markdownConvert -= value;
}
EventHandler<AIChatControlMessageSentEventArgs> messageSent;
public event EventHandler<AIChatControlMessageSentEventArgs> MessageSent {
add {
if(messageSent == null && Chat != null)
Chat.SetMessageSentCallback(RaiseMessageSent);
messageSent += value;
}
remove {
messageSent -= value;
if(messageSent == null && Chat != null)
Chat.SetMessageSentCallback(null);
}
}

public async Task SendMessage(string text, ChatRole role) {
if (Chat != null) {
await Chat.SendMessage(text, role);
Chat.Update();
}
}
public IEnumerable<BlazorChatMessage> SaveMessages() {
return Chat?.SaveMessages() ?? [];
}
public void LoadMessages(IEnumerable<BlazorChatMessage> messages) {
if (Chat != null) {
Chat.LoadMessages(messages);
Chat.Update();
}
}
public override void OnApplyTemplate() {
base.OnApplyTemplate();
this.chatWebView = GetTemplateChild("PART_ChatWebView") as ChatBlazorWebView;
ConfigureBlazorWebView();
}

void RaiseMessageSent(MessageSentEventArgs args) {
messageSent?.Invoke(this, new AIChatControlMessageSentEventArgs(Chat, args.Content));
}
MarkupString RaiseMarkdownConvert(string text) {
if(markdownConvert != null) {
var e = new AIChatControlMarkdownConvertEventArgs(text);
markdownConvert(this, e);
return e.HtmlText ?? new MarkupString();
}
return new MarkupString();
}
void OnUseStreamingChanged() {
if(Chat != null) {
Chat.UseStreaming = UseStreaming;
Chat.Update();
}
}
void OnContentFormatChanged() {
if(Chat != null) {
Chat.ResponseContentFormat = ContentFormat;
Chat.SetMarkdownConvertCallback(ContentFormat == ResponseContentFormat.Markdown ? RaiseMarkdownConvert : null);
Chat.Update();
}
}
void OnEmptyStateTextChanged() {
if(Chat != null) {
Chat.SetEmptyStateText(EmptyStateText);
Chat.Update();
}
}
void OnTemperatureChanged() {
if(Chat != null)
Chat.Temperature = Temperature;
}
void OnMaxTokensChanged() {
if(Chat != null)
Chat.MaxTokens = MaxTokens;
}
void OnFrequencyPenaltyChanged() {
if(Chat != null)
Chat.FrequencyPenalty = FrequencyPenalty;
}
void OnItemBackgroundChanged() {
if(Chat == null)
return;
Chat.Colors[ChatUIColor.UserMessageBackground] = ItemBackground.ToColor();
}
void OnControlBackgroundChanged() {
if(Chat == null)
return;
var controlBackground = ControlBackground.ToColor();
Chat.Colors[ChatUIColor.SubmitAreaBackground] = controlBackground;
Chat.Colors[ChatUIColor.InputBackground] = controlBackground;
Chat.Colors[ChatUIColor.AssistantMessageBackground] = controlBackground;
Chat.Colors[ChatUIColor.ButtonNormalBackground] = controlBackground;
Chat.Colors[ChatUIColor.ButtonDisabledBackground] = controlBackground;

}
void OnBackgroundChanged() {
if(Chat == null)
return;
var background = Background.ToColor();
Chat.Colors[ChatUIColor.Background] = background;
Chat.Colors[ChatUIColor.ButtonHoverBackground] = background;

}
void OnForegroundChanged() {
if(Chat == null)
return;
var foreground = Foreground.ToColor();
Chat.Colors[ChatUIColor.EmptyForeground] = foreground;
Chat.Colors[ChatUIColor.ScrollViewer] = foreground;
Chat.Colors[ChatUIColor.InputForeground] = foreground;
Chat.Colors[ChatUIColor.InputBorder] = Color.FromArgb((int)(255 * 0.25), foreground);
Chat.Colors[ChatUIColor.InputFocusShadow] = Color.FromArgb((int)(255 * 0.1), foreground);
Chat.Colors[ChatUIColor.MessageBorder] = Color.FromArgb((int)(255 * 0.25), foreground);
Chat.Colors[ChatUIColor.UserMessageForeground] = foreground;
Chat.Colors[ChatUIColor.AssistantMessageForeground] = foreground;

}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
base.OnPropertyChanged(e);
if(e.Property == BackgroundProperty)
OnBackgroundChanged();
if(e.Property == ForegroundProperty)
OnForegroundChanged();
}
void ConfigureBlazorWebView() {
incapsulationService = new DxChatIncapsulationService();
chatWebView.HostPage = StaticResourceIdentifiers.HostPageFilePath;
chatWebView.Services = GetServiceProvider(incapsulationService);
chatWebView.RootComponents.Add(new RootComponent() {
Selector = StaticResourceIdentifiers.AppDivId,
ComponentType = typeof(ChatUIWrapper),
Parameters = new Dictionary<string, object>() {
{ nameof(DxAIChat.Initialized), new EventCallback<IAIChat>(null, OnChatInitialized)}
}
});
blazorChatComponent = chatWebView.RootComponents[0];
}
ServiceProvider GetServiceProvider(DxChatIncapsulationService incapsulationService) {
var chatClientAIService = AIExtensionsContainerDesktop.Default.GetService<IChatClient>();
if(chatClientAIService == null)
throw new InvalidOperationException("There is no registered service of type Microsoft.Extensions.AI.IChatClient");
var aiAssistantFactory = AIExtensionsContainerDesktop.Default.GetService<IAIAssistantFactory>();
return ServiceProviderBuildHelper.BuildServiceProvider(incapsulationService,
s => s.AddWpfBlazorWebView(),
chatClientAIService, aiAssistantFactory);
}
void OnChatInitialized(IAIChat chat) {
var chatWrapper = chat as IChatUIWrapper;
if(chatWrapper == null)
return;
chatWrapper.UseStreaming = UseStreaming;
chatWrapper.ResponseContentFormat = ContentFormat;
chatWrapper.SetEmptyStateText(EmptyStateText);
chatWrapper.Temperature = Temperature;
chatWrapper.FrequencyPenalty = FrequencyPenalty;
chatWrapper.MaxTokens = MaxTokens;
if(messageSent != null)
chatWrapper.SetMessageSentCallback(RaiseMessageSent);
if(ContentFormat == ResponseContentFormat.Markdown)
chatWrapper.SetMarkdownConvertCallback(RaiseMarkdownConvert);
OnBackgroundChanged();
OnForegroundChanged();
OnControlBackgroundChanged();
OnItemBackgroundChanged();
}
}
static class ColorExtensions {
public static Color ToColor(this System.Windows.Media.Brush brush) {
return (brush as System.Windows.Media.SolidColorBrush)?.Color.ToColor() ?? Color.Black;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using DevExpress.AIIntegration.Blazor.Chat.WebView;
using Microsoft.AspNetCore.Components.WebView.Wpf;
using Microsoft.Extensions.FileProviders;

namespace WPF_AIChatControl {
internal class ChatBlazorWebView : BlazorWebView {
public override IFileProvider CreateFileProvider(string contentRootDir) {
return new EmbeddedFileProvider(
typeof(IChatUIWrapper).Assembly,
typeof(ChatUIWrapper).Namespace);
}
}
}

This file was deleted.

Loading
Loading