基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发2-功能开发

基于,avalonia,reactiveui,跨平台,项目,开发,功能 · 浏览次数 : 593

小编点评

#avalonia开发框架简介 **avalonia**是一个基于**C**的**跨平台****框架,它可以用于构建各种类型应用程序,包括**web**、**移动**、****平台**等。 **框架主要特点:** * **跨平台:**可以用于构建跨平台应用程序。 * **性能:**可以提供高质量的性能。 * **可扩展性:**可以进行扩展性操作。 * **模块化:**可以进行模块化操作。 **框架主要框架:** * **C:**用于编写应用程序代码。 * **C++:**用于编写应用程序代码。 * **Java:**用于编写应用程序代码。 * **Kotlin:**用于编写应用程序代码。 **框架使用指南:** * **C:**使用C语言编写应用程序代码。 * **C++:**使用C++语言编写应用程序代码。 * **Java:**使用Java语言编写应用程序代码。 * **Kotlin:**使用Kotlin语言编写应用程序代码。 **框架开发框架:** * **TerraMours.Chat.Ava:**用于构建聊天应用程序。 * **ChatGPT:**用于构建聊天机器人。 #总结 avalonia是一个功能强大的框架,可以用于构建各种类型应用程序。框架使用指南清晰易懂,开发框架提供开发者很多可以帮助的工具和方法。 **未来开发:** * 实现跨平台应用程序。 *优化性能。 *扩展性。 *模块化。 **其他信息:** * 官方网站:**raokun.top** * GitHub项目地址:**github.com/raokun/TerraMours.Chat.Ava** *开源项目地址:**github.com/raokun/TerraMours.Chat.Ava** #希望这些信息能帮助您!

正文

基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发2-功能开发

image-20230718225201652

项目简介:目标是开发一个跨平台的AI聊天和其他功能的客户端平台。目的来学习和了解Avalonia。将这个项目部署在openKylin 1.0 的系统上。

为什么使用Avalonia:之前已经了解了基于Avalonia的项目在国产麒麟系统中运行的案例。正是Avalonia在跨平台的出色表现,学习和了解Avalonia这个UI框架显得十分有必要。本项目采用的是最新稳定版本11.0.0-rc1.1。希望通过该项目了解和学习Avalonia开发的朋友可以在我的github上拉取代码,同时希望大家多多点点star。

https://github.com/raokun/TerraMours.Chat.Ava

项目的基础框架和通用功能在上一篇博客中介绍过了,想了解的同学跳转学习:

基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发1-通用框架

了解Avalonia创建模板项目-基础可跳转:

创建Avalonia 模板项目-基础

本次我主要分享的内容是项目中具体功能开发实现的过程和各技术的应用

1.功能介绍

1.界面交互

第一版的内容主要分为以下几个模块:

  • LoadView.axaml 加载界面:系统打开时候的加载界面,用于首页替换的技术实践。可改造成登陆界面。
  • MainWindow.axaml 首页
  • MainView.axaml 主界面
  • DataGridView.axaml 会话列表
  • ChatView.axaml 聊天界面
  • ApiSettingsView.axaml API配置

2.功能实现

下面我会按照各个模块来介绍对应的功能和实现方法。

1.加载界面

加载界面 是系统的首个加载界面,界面样式如下:

image-20230718225041733

1.作用和功能:

加载界面是系统在运行前的准备界面,目前并没有做什么操作,只是做了个进度条,到100%时跳转首页。不过这是一个可扩展的实践。

加载界面完成了首页的切换的实践,为后期登录页面做好了准备。同时,加载界面的内容,改写成蒙版,在需要长时间数据处理用于限制用户操作也是不错的选择。

2.设置加载界面为项目运行时首个加载界面

设置首个加载界面,需要在App.axaml.cs中的OnFrameworkInitializationCompleted方法中设置 desktop.MainWindow

image-20230719000542962

OnFrameworkInitializationCompleted代码如下:

public override void OnFrameworkInitializationCompleted() {
    //添加共享资源
    var VMLocator = new VMLocator();
    Resources.Add("VMLocator", VMLocator);

    if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
        var load= new LoadView {
            DataContext = new LoadViewModel(),
        };
        desktop.MainWindow = load;
        VMLocator.LoadViewModel.ToMainAction = () =>
        {
            desktop.MainWindow = new MainWindow();
            desktop.MainWindow.Show();
            load.Close();
        };

    }

    base.OnFrameworkInitializationCompleted();
}

3.隐藏窗口的关闭按钮,并设置窗口居中显示

加载界面不应该有关闭等按钮,我们用 SystemDecorations="None"。将 SystemDecorations 属性设置为 "None" 可以隐藏窗口的系统装饰。系统装饰包括标题栏、最小化、最大化和关闭按钮等。通过设置 SystemDecorations"None",可以使窗口更加定制化和个性化,同时减少了不必要的系统装饰。

界面应该显示在屏幕正中间。我们用 WindowStartupLocation="CenterScreen"。设置 WindowStartupLocation"CenterScreen" 可以使窗口在屏幕上居中显示。

image-20230718231841408

4.实现进度条

image-20230718234226513

代码如下:

<StackPanel
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Orientation="Vertical"
            Spacing="10">
    <TextBlock
               Text="Loading..."
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Foreground="White"
               Background="Transparent"/>
    <ProgressBar
                 x:Name="progressBar"
                 HorizontalAlignment="Center"
                 Minimum="0"
                 Maximum="100"
                 Value="{Binding Progress}"
                 Width="200"
                 Height="20"
                 Background="Transparent">
        <ProgressBar.Foreground>
            <SolidColorBrush Color="White"/>
        </ProgressBar.Foreground>
    </ProgressBar>
</StackPanel>

进度条用到了ProgressBar 的控件,对应的官方文档地址:ProgressBar

控件的属性:

Property Description
Minimum 最大值
Maximum 最小值
Value 当前值
Foreground 进度条颜色
ShowProgressText 显示进度数值

Value 值通过Binding绑定了ViewModel中的属性字段Progress。通过UpdateProgress()方法,让Progress的值由0变化到100,模拟加载过程。

image-20230718234825365

代码如下:

private async void UpdateProgress() {
    // 模拟登录加载过程
    for (int i = 0; i <= 100; i++) {
        Progress = i;
        await Task.Delay(100); // 延迟一段时间,以模拟加载过程
    }
    ToMainAction?.Invoke();
}

5.加载完成后跳转首页

image-20230718235630454

界面的跳转,通过Action委托来完成,首先在LoadViewModel中定义 ToMainAction,在上面的UpdateProgress方法完成时执行Invoke,而ToMainAction的实现方法,写在OnFrameworkInitializationCompleted方法中。

image-20230718235414637

ToMainAction的实现方法中,将desktop.MainWindow变更成MainWindowloadView隐藏,MainWindow显示。

2.首页+API配置

加载界面 是承载程序的界面,界面样式如下:

image-20230719144805612

1.作用和功能:

首页 主要作用是承载程序的界面,每一个Avalonia项目在创建时会自动创建MainWindow.axaml 在界面axaml中很简单。承载了MainView 的用户控件,和API设置界面。

首页 包括控制API设置的数据交互、键盘的监听事件、系统语言的判断。

API配置 包括用于OpenAI接口调用参数的全部设置。

2.界面设计-设置弹框

image-20230719143401064

代码如下:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:TerraMours.Chat.Ava.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="TerraMours.Chat.Ava.Views.MainWindow"
		x:DataType="vm:MainWindowViewModel"
		xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
		RenderOptions.BitmapInterpolationMode="HighQuality"
		xmlns:sty="using:FluentAvalonia.Styling"
		xmlns:ui="using:FluentAvalonia.UI.Controls"
		xmlns:local="using:TerraMours.Chat.Ava.Views"
        Icon="/Assets/terramours.ico"
        Title="TerraMours.Chat.Ava">
	<dialogHost:DialogHost IsOpen="{Binding	ApiSettingIsOpened}"
						   DialogMargin="16"
							DisableOpeningAnimation="True"
						   dialogHost:DialogHostStyle.CornerRadius="8"
						   Background="rgb(52, 53, 65)">
		<dialogHost:DialogHost.DialogContent>
			<local:ApiSettingsView />
		</dialogHost:DialogHost.DialogContent>

		<Panel>
			<local:MainView />
		</Panel>

	</dialogHost:DialogHost>

</Window>

界面中使用到了 DialogHost.Avalonia,做弹框的简单实现。IsOpen 控制弹窗的显隐性。DialogHost.DialogContent 中填入弹框的显示内容。显示内容为

ApiSettingsView

而主体部分只有一个Panel,包含着MainView,这使得MainView的界面会占满整个程序界面。至此,首页的界面设计就完成了。

Icon="/Assets/terramours.ico" 设置了程序的logo,如下:

image-20230719145303993

3.初始化

MainWindow 在界面加载时要做很多工作。MainWindow的构造函数如下:

image-20230719150604374

代码如下:

public MainWindow() {
            InitializeComponent();
            this.Closing += (sender, e) => SaveWindowSizeAndPosition();

            this.Loaded += MainWindow_Loaded;
            MainWindowViewModel = new MainWindowViewModel();
            VMLocator.MainWindowViewModel = MainWindowViewModel;
            DataContext = MainWindowViewModel;
            var cultureInfo = CultureInfo.CurrentCulture;
            if (cultureInfo.Name == "zh-CN") {
                Translate("zh-CN");
            }

            this.KeyDown += MainWindow_KeyDown;

        }

MainWindow的构造函数绑定了多个事件的实现方法:

  1. this.Loaded 界面加载时触发MainWindow_Loaded 方法,作用是加载本地数据和本地配置文件。
  2. this.Closing 程序关闭时触发SaveWindowSizeAndPosition方法,作用是保存当前的系统设置,包括用户调整后的界面的长宽和在屏幕的位置,用户下次打开时程序还会出现在之前的位置。比如,我把系统拉到桌面左上角,把窗口缩小到最小尺寸时候退出程序,下次打开,程序界面还会在之前退出的位置,在桌面左上角以最小尺寸出现。
  3. this.KeyDown 监听键盘的输入事件,当按键按下时,会触发MainWindow_KeyDown方法,用于绑定自定义的快捷键。

MainWindow构造函数中,通过判断CultureInfo.CurrentCulture,获取当前 操作系统的语言系统,判断程序应该显示哪个国家的语言。从而确定程序显示的语言,通过Translate 修改语言相关配置。是系统国际化的实践。

4.系统配置-本地保存和加载

系统配置 对应AppSettings类,记录了应用程序设置ChatGPT API参数

image-20230719150155309

系统设置参数通过保存到文件settings.json中实现配置的本地化和持久化。

MainWindow_Loaded 的方法实现系统配置加载:

image-20230719152508260

代码如下:

private async void MainWindow_Loaded(object sender,RoutedEventArgs e) {
    var settings = await LoadAppSettingsAsync();

    if (File.Exists(Path.Combine(settings.AppDataPath, "settings.json"))) {
        this.Width = settings.Width - 1;
        this.Position = new PixelPoint(settings.X, settings.Y);
        this.Height = settings.Height;
        this.Width = settings.Width;
        this.WindowState = settings.IsMaximized ? WindowState.Maximized : WindowState.Normal;
    }
    else {
        var screen = Screens.Primary;
        var workingArea = screen.WorkingArea;

        double dpiScaling = screen.PixelDensity;
        this.Width = 1300 * dpiScaling;
        this.Height = 840 * dpiScaling;

        this.Position = new PixelPoint(5, 0);
    }


    if (!File.Exists(settings.DbPath)) {
        _dbProcess.CreateDatabase();
    }

    await _dbProcess.DbLoadToMemoryAsync();
    await VMLocator.MainViewModel.LoadPhraseItemsAsync();

    VMLocator.MainViewModel.SelectedPhraseItem = settings.PhrasePreset;

    VMLocator.MainViewModel.SelectedLogPain = "Chat List";

    await Dispatcher.UIThread.InvokeAsync(() => { VMLocator.MainViewModel.LogPainIsOpened = false; });
    if (this.Width > 1295) {
        //await Task.Delay(1000);
        await Dispatcher.UIThread.InvokeAsync(() => { VMLocator.MainViewModel.LogPainIsOpened = true; });
    }

    this.GetObservable(ClientSizeProperty).Subscribe(size => OnSizeChanged(size));
    _previousWidth = ClientSize.Width;

    await _dbProcess.UpdateChatLogDatabaseAsync();


    await _dbProcess.CleanUpEditorLogDatabaseAsync();

    if (string.IsNullOrWhiteSpace(VMLocator.MainWindowViewModel.ApiKey)) {
        var dialog = new ContentDialog() { Title = $"Please enter your API key.", PrimaryButtonText = "OK" };
        await VMLocator.MainViewModel.ContentDialogShowAsync(dialog);
        VMLocator.ChatViewModel.OpenApiSettings();
    }
}

MainWindow_Loaded 的方法中,通过解析settings.json,加载系统配置。

settings.json 的解析和保存

相关方法如下:

image-20230719153049144

至此,系统配置的开发就基本完成了。对于这些不需要远程同步的基础配置,保存在本地文件中。

5.国际化

通过Translate方法,根据当前系统语言,改变控制文字显示的资源文件,实现语言的切换。

image-20230719155428155

代码如下:

public void Translate(string targetLanguage) {
    var translations = App.Current.Resources.MergedDictionaries.OfType<ResourceInclude>().FirstOrDefault(x => x.Source?.OriginalString?.Contains("/Lang/") ?? false);

    if (translations != null)
        App.Current.Resources.MergedDictionaries.Remove(translations);

    App.Current.Resources.MergedDictionaries.Add(
        (ResourceDictionary)AvaloniaXamlLoader.Load(
            new Uri($"avares://TerraMours.Chat.Ava/Assets/lang/{targetLanguage}.axaml")
        )
    );
}

关于国际化的资源文件的创建请看前篇内容:基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发1-通用框架

3.主界面

主界面 是项目的核心,包括了以下图片所有内容的布局,它勾勒出了整个程序。中间包括左上角的图标,会话列表,聊天区域,查询,配置等等。

image-20230718225201652

1.作用和功能

主界面 的作用,是显示和完成整个业务功能的展示和交互。主界面 将会话列表和聊天窗口左右分开。控制了整个程序的排版和布局。

image-20230719173323402

主要分三块:

  1. 程序标题logo
  2. 会话列表
  3. 聊天窗口

2.界面设计

image-20230719173721437

具体代码不贴出来了,需要了解的同学可以fork项目代码查看,功能区已经标注注释了,方便查看。

3.SplitView控制会话列表显示

会话列表聊天窗口 通过SplitView 实现,会话列表在窗口缩小时自动隐藏。通过IsPaneOpen属性控制。

隐藏效果:

image-20230719174600833

实现方法为OnSizeChanged方法:

image-20230719174645433

代码如下:

 private void OnSizeChanged(Size newSize) {
     if (_previousWidth != newSize.Width) {
         if (newSize.Width <= 1295) {
             VMLocator.MainViewModel.LogPainIsOpened = false;
             VMLocator.MainViewModel.LogPainButtonIsVisible = false;
         }
         else {
             if (VMLocator.MainViewModel.LogPainButtonIsVisible == false) {
                 VMLocator.MainViewModel.LogPainButtonIsVisible = true;
             }
             if (newSize.Width > _previousWidth) {
                 VMLocator.MainViewModel.LogPainIsOpened = true;
             }
         }
         _previousWidth = newSize.Width;
     }
 }

当窗口宽度小于1295,会修改VMLocator.MainViewModel.LogPainButtonIsVisible为false,实现会话列表隐藏的效果。

4.初始化

MainViewModel控制了程序大部分的按键的事件实现,MainViewModel的构造函数如下:

image-20230719175328066

代码如下:

 public MainViewModel() {
     PostButtonText = "Post";

     LoadChatListCommand = ReactiveCommand.CreateFromTask<string>(async (keyword) => await LoadChatListAsync(keyword));
     PhrasePresetsItems = new ObservableCollection<string>();

     //会话
     ImportChatLogCommand = ReactiveCommand.CreateFromTask(ImportChatLogAsync);
     ExportChatLogCommand = ReactiveCommand.CreateFromTask(ExportChatLogAsync);
     DeleteChatLogCommand = ReactiveCommand.CreateFromTask(DeleteChatLogAsync);
     //配置
     SystemMessageCommand = ReactiveCommand.Create(InsertSystemMessage);
     HotKeyDisplayCommand = ReactiveCommand.CreateFromTask(HotKeyDisplayAsync);
     OpenApiSettingsCommand = ReactiveCommand.Create(OpenApiSettings);
     ShowDatabaseSettingsCommand = ReactiveCommand.CreateFromTask(ShowDatabaseSettingsAsync);
     //聊天
     PostCommand = ReactiveCommand.CreateFromTask(PostChatAsync);
 }

其中,绑定了会话、配置、聊天等功能的按钮事件。实现业务的交互。

5.调用ChatGpt接口

通过Betalgo.OpenAI 完成接口调用,是一个开源的nuget包,集成了OpenAI的接口,简化了调用逻辑。

本来更倾向于Senmantic Kernel的,是微软开发的LLM训练框架,但是代理方面我还没有很好的解决办法,后面再替换。

接口调用方法写在PostChatAsync方法里,通过post按钮发起调用:

image-20230719200445744

代码如下:

/// <summary>
/// OpenAI 调用方法
/// </summary>
/// <returns></returns>
private async Task PostChatAsync()
{
    try
    {
        string message = PostMessage;
        int conversationId = 1;
        //创建会话
        if(VMLocator.DataGridViewModel.ChatList == null)
        {
            VMLocator.DataGridViewModel.ChatList=new ObservableCollection<ChatList> ();
            VMLocator.DataGridViewModel.ChatList.Add(new ChatList() { Id=1,Title=(message.Length< 5?message:$"{message.Substring(0,5)}..."), Category = (message.Length < 5 ? message : $"{message.Substring(0, 5)}...") ,Date=DateTime.Now});
        }
        if (VMLocator.ChatViewModel.ChatHistory == null)
            VMLocator.ChatViewModel.ChatHistory = new ObservableCollection<Models.ChatMessage>();
        VMLocator.ChatViewModel.ChatHistory.Add(new Models.ChatMessage() { ChatRecordId = 1, ConversationId = conversationId, Message = message, Role = "User", CreateDate = DateTime.Now });

        //根据配置中的CONTEXT_COUNT 查询上下文
        var messages = new List<OpenAI.ObjectModels.RequestModels.ChatMessage>();
        messages.Add(OpenAI.ObjectModels.RequestModels.ChatMessage.FromUser(message));
        var openAiOpetions = new OpenAI.OpenAiOptions()
        {
            ApiKey = AppSettings.Instance.ApiKey,
            BaseDomain = AppSettings.Instance.ApiUrl
        };
        var openAiService = new OpenAIService(openAiOpetions);
        //调用SDK
        var response = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
                                                                           {
                                                                               Messages = messages,
                                                                               Model = AppSettings.Instance.ApiModel,
                                                                               MaxTokens = AppSettings.Instance.ApiMaxTokens,
                                                                           });
        if (response == null)
        {
            var dialog = new ContentDialog()
            {
                Title = "接口调用失败",
                PrimaryButtonText = "Ok"
            };
            await VMLocator.MainViewModel.ContentDialogShowAsync(dialog);
        }
        if (!response.Successful)
        {
            var dialog = new ContentDialog()
            {
                Title = $"接口调用失败,报错内容: {response.Error.Message}",
                PrimaryButtonText = "Ok"
            };
            await VMLocator.MainViewModel.ContentDialogShowAsync(dialog);
        }
        VMLocator.ChatViewModel.ChatHistory.Add(new Models.ChatMessage() { ChatRecordId = 2, ConversationId = conversationId, Message = response.Choices.FirstOrDefault().Message.Content, Role = "Assistant", CreateDate = DateTime.Now });
        VMLocator.MainViewModel.PostMessage = "";
    }
    catch (Exception e)
    {
    }
}

通过创建OpenAIService初始化,Completion接口调用时使用openAiService.ChatCompletion.CreateCompletion方法。

ChatMessage是上下文的模型,通过创建messages完成上下文的创建,请求参数都写在ChatCompletionCreateRequest之中。

目前的第一版使用的CreateCompletion是直接返回的结果。后面我会优化调用,使用Stream流式输出。

4.会话列表

会话列表是模拟chatgpt官网的样式,将聊天按会话的形式归类。chatgpt官网截图如下:

image-20230719201304205

1.作用和功能

会话列表将聊天按会话的形式归类,更好的管理聊天内容。

2.界面设计

因为考虑到后面会有其他类型的AI 类型,决定通过DataGrid实现会话列表,DataGrid的表格类型也能更多的展示数据。

image-20230719201810771

代码如下:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
			 xmlns:vm="using:TerraMours.Chat.Ava.ViewModels"
		     x:DataType="vm:DataGridViewModel"
			 xmlns:local="using:TerraMours.Chat.Ava"
             x:Class="TerraMours.Chat.Ava.Views.DataGridView">
	<UserControl.Resources>
		<local:CustomDateConverter x:Key="CustomDateConverter" />
	</UserControl.Resources>
	<Grid>
		<DataGrid Name="ChatListDataGrid"
				  ItemsSource="{Binding ChatList}"
				  AutoGenerateColumns="False"
				  HeadersVisibility="None"
				  SelectionMode="Single"
				  SelectedItem="{Binding SelectedItem}"
				  SelectedIndex="{Binding SelectedItemIndex}">
			<DataGrid.Columns>
				<DataGridTextColumn Binding="{Binding Id}" IsVisible="False"/>
				<DataGridTextColumn Foreground="rgb(155,155,155)"
								  FontSize="12"
									Binding="{Binding Date,Converter={StaticResource CustomDateConverter},Mode=OneWay}"
									IsReadOnly="True"/>
				<DataGridTextColumn Binding="{Binding Title}" IsReadOnly="True"/>
			</DataGrid.Columns>
		</DataGrid>
	</Grid>
</UserControl>

会话列表的数据共有三个:Id,创建时间,会话标题。通过DataGridTextColumn 通过绑定的形式实现。

其中,使用了CustomDateConverter 时间转换器,将Date的显示格式做转换。

3.数据交互

会话列表目前还在优化,第一版是通过第一次调用PostChatAsync时创建的。目前的数据存在本地SQLite数据库中。

5.聊天窗口

聊天窗口是程序的工作重心,是展示聊天成果的重要界面。其中用到Markdown.Avalonia的扩展包实现Markdown内容的展示。

image-20230719202912320

1.作用和功能

数据是核心,聊天窗口是数据的展示平台,作用不容小嘘。通过编写数据模板DataTemplate来控制内容的展示。呈现chat问答式的结果。

2.Markdown 风格样式

通过DataTemplate来设置Markdown 风格样式。代码如下:

<DataTemplate>
    <Border
    Name="MessageBorder"
    Background="{Binding Role, Converter={StaticResource ChatBackgroundConverter}}"
    HorizontalAlignment="Left"
    Padding="5"
    Margin="20,5,20,20"
    CornerRadius="8,8,8,0">
    <md:MarkdownScrollViewer
        VerticalAlignment="Stretch"
        MarkdownStyleName="Standard"
        SaveScrollValueWhenContentUpdated="True"
        TextElement.FontSize="16"
        TextElement.Foreground="White"
        Markdown="{Binding Message}">
        <md:MarkdownScrollViewer.Styles>
            <Style Selector="ctxt|CCode">
            <Style.Setters>
            <Setter Property="BorderBrush"         Value="Green"/>
            <Setter Property="BorderThickness"     Value="2"/>
            <Setter Property="Padding"             Value="2"/>
            <Setter Property="MonospaceFontFamily" Value="Meiryo" />
            <Setter Property="Foreground"          Value="DarkGreen" />
            <Setter Property="Background"          Value="LightGreen" />
            </Style.Setters>
            </Style>

            <Style Selector="Border.CodeBlock">
            <Style.Setters>
            <Setter Property="BorderBrush" Value="#E2E6EA" />
            <Setter Property="BorderThickness" Value="0,30,0,0" />
            <Setter Property="Margin" Value="5,0,5,0" />
            <Setter Property="Background" Value="Black" />
            </Style.Setters>
            </Style>

            <Style Selector="TextBlock.CodeBlock">
            <Style.Setters>
            <Setter Property="Background" Value="Black" />
            </Style.Setters>
            </Style>

            <Style Selector="avedit|TextEditor">
            <Style.Setters>
            <Setter Property="BorderBrush" Value="#E2E6EA" />
            <Setter Property="Background" Value="Black" />
            <Setter Property="Padding" Value="5"></Setter>
            </Style.Setters>
            </Style>

            </md:MarkdownScrollViewer.Styles>
                <md:MarkdownScrollViewer.ContextMenu>
                    <ContextMenu Padding="3">
                    <MenuItem>
                    <MenuItem.Header>
                    <TextBlock>编辑</TextBlock>
                    </MenuItem.Header>
                    </MenuItem>
                    <!--<MenuItem Tag="{Binding ChatRecordId}" Click="DeleteClick">
                    <MenuItem.Header>
                    <TextBlock>删除</TextBlock>
                    </MenuItem.Header>
                    </MenuItem>
                    <MenuItem Tag="{Binding Message}" Click="CopyClick">
                    <MenuItem.Header>
                    <TextBlock>复制</TextBlock>
                    </MenuItem.Header>
                    </MenuItem>-->
                    </ContextMenu>
                    </md:MarkdownScrollViewer.ContextMenu>
                        </md:MarkdownScrollViewer>
                            </Border>
                            </DataTemplate>

MarkdownScrollViewer.Styles 根据不同的内容设置不同的样式。

MarkdownScrollViewer.ContextMenu设置右键菜单。

其中通过ChatBackgroundConverter转换器根据角色控制背景,ChatBackgroundConverter代码如下:

image-20230719203600760

3.总结和待办事项

avalonia开发目前网上,特别是国内的网站的教程和文章很少,希望能给大家一点学习使用avalonia开发客户端项目的朋友一点帮助。写的不对的地方也恳请大家多多留言,我会及时更正,多多交流心得体会。

Todo:

  1. 项目发布,在多平台下的运行
  2. 搭建国产系统虚拟机测试avalonia项目
  3. 程序改造成云同步版本,跟我做的web项目互通。
  4. 优化UI界面
  5. 优化语言国际化内容

**目前程序还没有完全开发完成。后续的开发我会及时跟进。阅读如遇样式问题,请前往个人博客浏览:https://www.raokun.top

目前web端ChatGPT:https://ai.terramours.site

当前开源项目地址:https://github.com/raokun/TerraMours.Chat.Ava

与基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发2-功能开发相似的内容:

基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发1-通用框架

# 基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发1-通用框架 ### Avalonia简介: Avalonia是.NET的一个跨平台UI框架,提供了一个灵活的样式系统,支持广泛的操作系统,如Windows、Linux、macOS,并对Android、iOS和WebAss

基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发2-功能开发

# 基于Avalonia 11.0.0+ReactiveUI 的跨平台项目开发2-功能开发 ![image-20230718225201652](https://www.raokun.top/upload/2023/07/image-20230718225201652.png) **项目简介**:目

基于.Net 的 AvaloniUI 多媒体播放器方案汇总

基于.Net 的 AvaloniUI 多媒体播放器方案汇总 摘要 随着国产化的推进,相信.Net的桌面端的小伙伴的可能已经有感受到了。 为了让.Net的桌面框架能够跨桌面平台,首选的就是Avalona-UI。 为了让AvaloniaUI能够跨多个平台播放视频,这里测试主要播放视频形式是使用RTSP。

分享下最近基于Avalonia UI和MAUI写跨平台时间管理工具的体验

起因 几个月前,我在寻找一款时间管理软件,类似番茄时钟的工具,但是希望可以自定义时间。 需要自定义的场景 做雅思阅读,3篇文件需要严格控制时间分配,需要一个灵活的计时器 定期提醒,每30分钟需要喝水或者上个厕所或者摸一下鱼... 总结起来就是:专注一段时间,比如30分钟,然后休息10分钟,且没有杂七

跨平台`ChatGpt` 客户端

跨平台ChatGpt 客户端 一款基于Avalonia实现的跨平台ChatGpt客户端 ,通过对接ChatGpt官方提供的ChatGpt 3.5模型实现聊天对话 实现创建ChatGpt的项目名称 ,项目类型是Avalonia MVVM , 添加项目需要使用的Nuget包

动手学Avalonia:基于硅基流动构建一个文生图应用(一)

文生图 文生图,全称“文字生成图像”(Text-to-Image),是一种AI技术,能够根据给定的文本描述生成相应的图像。这种技术利用深度学习模型,如生成对抗网络(GANs)或变换器(Transformers),来理解和解析文本中的语义信息,并将其转化为视觉表现。文生图可以用于创意设计、图像编辑、虚

动手学Avalonia:基于SemanticKernel与硅基流动构建AI聊天与翻译工具

Avalonia是什么? Avalonia是一个跨平台的UI框架,专为.NET开发打造,提供灵活的样式系统,支持Windows、macOS、Linux、iOS、Android及WebAssembly等多种平台。它已成熟并适合生产环境,被Schneider Electric、Unity、JetBrai

Avalonia应用在基于Linux的国产操作deepin上运行

deepin系统介绍 deepin(原名Linux Deepin)致力于为全球用户提供美观易用,安全可靠的 Linux发行版。deepin项目于2008年发起,并在2009年发布了以 linux deepin为名称的第一个版本。2014年4月更名为 deepin,在中国常被称为“深度操作系统”。 截

【译】基于XAML的跨平台框架对比分析

多年来,基于XAML的UI框架已经有了很大的发展。下面的图表是最好的说明。这些框架主要包含:支持跨平台应用的Avalonia UI, Uno Platform和 .NET MAUI。事实上,除了Avalonia UI之外,对跨平台XAML的需求是其发展的主要驱动力。如果微软早点推出一个类似Flutt

.NET跨平台框架选择之一 - Avalonia UI

本文阅读目录 1. Avalonia UI简介 Avalonia UI文档教程:https://docs.avaloniaui.net/docs/getting-started 随着跨平台越来越流行,.NET支持跨平台至今也有十几年的光景了(Mono开始)。 但是目前基于.NET的跨平台,大多数还是