• 跨平台`ChatGpt` 客户端


    跨平台ChatGpt 客户端

    一款基于Avalonia实现的跨平台ChatGpt客户端 ,通过对接ChatGpt官方提供的ChatGpt 3.5模型实现聊天对话

    实现创建ChatGpt的项目名称 ,项目类型是Avalonia MVVM

    添加项目需要使用的Nuget

    
        <ItemGroup>
            <PackageReference Include="Avalonia" Version="11.0.0-preview5" />
            <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview5" />
            <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview5" />
            
            <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview5" />
            <PackageReference Include="FreeSql.Provider.Sqlite" Version="3.2.690" />
            <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
            <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
            <PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
            <PackageReference Include="Avalonia.Svg.Skia" Version="11.0.0-preview5" />
        ItemGroup>
    
    

    ViewLocator.cs代码修改

    using System;
    using Avalonia.Controls;
    using Avalonia.Controls.Templates;
    using ChatGPT.ViewModels;
    
    namespace ChatGPT;
    
    public class ViewLocator : IDataTemplate
    {
        public Control? Build(object? data)
        {
            if (data is null)
                return null;
    
            var name = data.GetType().FullName!.Replace("ViewModel", "View");
            var type = Type.GetType(name);
    
            if (type != null)
            {
                return (Control)Activator.CreateInstance(type)!;
            }
    
            return new TextBlock { Text = name };
        }
    
        public bool Match(object? data)
        {
            return data is ViewModelBase;
        }
    }
    

    创建MainApp.cs文件

    using Microsoft.Extensions.DependencyInjection;
    
    namespace ChatGPT;
    
    public static class MainApp
    {
        private static IServiceProvider ServiceProvider;
    
        public static ServiceCollection CreateServiceCollection()
        {
            return new ServiceCollection();
        }
    
        public static IServiceProvider Build(this IServiceCollection services)
        {
            return ServiceProvider = services.BuildServiceProvider();
        }
        
        public static T GetService()
        {
            if (ServiceProvider is null)
            {
                throw new ArgumentNullException(nameof(ServiceProvider));
            }
            return ServiceProvider.GetService();
        }
        
        public static IEnumerable GetServices()
        {
            if (ServiceProvider is null)
            {
                throw new ArgumentNullException(nameof(ServiceProvider));
            }
            return ServiceProvider.GetServices();
        }
    
        public static object? GetService(Type type)
        {
            if (ServiceProvider is null)
            {
                throw new ArgumentNullException(nameof(ServiceProvider));
            }
            return ServiceProvider.GetService(type);
        }
    }
    

    创建GlobalUsing.cs文件 全局引用

    global using System.Reactive;
    global using Avalonia;
    global using Avalonia.Controls;
    global using ChatGPT.ViewModels;
    global using Avalonia;
    global using Avalonia.Controls.ApplicationLifetimes;
    global using Avalonia.Markup.Xaml;
    global using ChatGPT.ViewModels;
    global using ChatGPT.Views;
    global using System;
    global using System.Collections.Generic;
    global using ReactiveUI;
    

    修改App.axaml代码文件

    <Application xmlns="https://github.com/avaloniaui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:local="using:ChatGPT"
                 xmlns:converter="clr-namespace:ChatGPT.Converter"
                 RequestedThemeVariant="Light"
                 x:Class="ChatGPT.App">
        <Application.Resources>
            <converter:HeightConverter x:Key="HeightConverter" />
        Application.Resources>
        <Application.DataTemplates>
            <local:ViewLocator/>
        Application.DataTemplates>
    
        <Application.Styles>
            <FluentTheme DensityStyle="Compact"/>
        Application.Styles>
        
    Application>
    
    

    修改App.axaml.cs代码文件

    using Avalonia.Platform;
    using Avalonia.Svg.Skia;
    using ChatGPT.Options;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace ChatGPT;
    
    public partial class App : Application
    {
        public override void Initialize()
        {
            GC.KeepAlive(typeof(SvgImageExtension).Assembly);
            GC.KeepAlive(typeof(Avalonia.Svg.Skia.Svg).Assembly);
    
            var services = MainApp.CreateServiceCollection();
    
            services.AddHttpClient("chatGpt")
                .ConfigureHttpClient(options =>
                {
                    var chatGptOptions = MainApp.GetService();
                    if (!string.IsNullOrWhiteSpace(chatGptOptions?.Token))
                    {
                        options.DefaultRequestHeaders.Add("Authorization",
                            "Bearer " + chatGptOptions?.Token.TrimStart().TrimEnd());
                    }
                });
    
            services.AddSingleton(ChatGptOptions.NewChatGptOptions());
    
            services.AddSingleton(new FreeSql.FreeSqlBuilder()
                .UseConnectionString(FreeSql.DataType.Sqlite,
                    "Data Source=chatGpt.db;Pooling=true;Min Pool Size=1")
                .UseAutoSyncStructure(true) //自动同步实体结构到数据库
                .Build());
    
            services.Build();
    
            AvaloniaXamlLoader.Load(this);
        }
    
        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                desktop.MainWindow = new MainWindow
                {
                    DataContext = new MainViewModel()
                };
            }
    
            var notifyIcon = new TrayIcon();
            notifyIcon.Menu ??= new NativeMenu();
            notifyIcon.ToolTipText = "ChatGPT";
    
            var assets = AvaloniaLocator.Current.GetService();
    
            notifyIcon.Icon = new WindowIcon(assets.Open(new Uri("avares://ChatGPT/Assets/chatgpt.ico")));
            var exit = new NativeMenuItem()
            {
                Header = "退出ChatGPT"
            };
    
            exit.Click += (sender, args) => Environment.Exit(0);
            notifyIcon.Menu.Add(exit);
    
            base.OnFrameworkInitializationCompleted();
        }
    }
    

    修改MainWindow.axaml文件

    <Window 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"
            xmlns:viewModels="clr-namespace:ChatGPT.ViewModels"
            xmlns:pages="clr-namespace:ChatGPT.Pages"
            mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
            x:Class="ChatGPT.Views.MainWindow"
            ExtendClientAreaToDecorationsHint="True"
            ExtendClientAreaChromeHints="NoChrome"
            ExtendClientAreaTitleBarHeightHint="-1"
            Height="{Binding Height}"
            MinHeight="500"
            MinWidth="800"
            Width="1060"
            Name="Main">
    
        <Design.DataContext>
            <viewModels:MainViewModel />
        Design.DataContext>
    
        <StackPanel Name="StackPanel" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <WrapPanel Name="WrapPanel" VerticalAlignment="Stretch" Height="{Binding ElementName=StackPanel, Path=Height}">
                <StackPanel MaxWidth="55" Width="55">
                    <DockPanel Background="#2E2E2E" Height="{Binding Height}">
                        <StackPanel DockPanel.Dock="Top">
                            <StackPanel Margin="0,32,0,0">StackPanel>
                            <StackPanel Margin="8">
                                <Image Source="/Assets/avatar.png">Image>
                            StackPanel>
                            <StackPanel Name="ChatStackPanel" Margin="15">
                                <Image Source="/Assets/chat-1.png">Image>
                            StackPanel>
                        StackPanel>
    
                        <StackPanel Margin="5" VerticalAlignment="Bottom" DockPanel.Dock="Bottom">
                            <StackPanel VerticalAlignment="Bottom" Name="FunctionStackPanel">
                                <Menu HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                                    <MenuItem HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                                        <MenuItem.Header>
                                            <Image Margin="5" Height="20" Width="20" Source="/Assets/function.png">Image>
                                        MenuItem.Header>
                                        <MenuItem Click="Setting_OnClick" Name="Setting" Header="设置" />
                                    MenuItem>
                                Menu>
                            StackPanel>
                        StackPanel>
                    DockPanel>
                StackPanel>
    
                <Border Width="250" MaxWidth="250" BorderBrush="#D3D3D3" BorderThickness="0,0,1,0">
                    <StackPanel>
                        <pages:ChatShowView Name="ChatShowView"/>
                    StackPanel>
                Border>
    
                <StackPanel>
                    <StackPanel Height="{Binding Height}" HorizontalAlignment="Center" VerticalAlignment="Center">
                        <pages:SendChat DataContext="{Binding SendChatViewModel}">pages:SendChat>
                    StackPanel>
                StackPanel>
            WrapPanel>
        StackPanel>
    Window>
    

    修改MainWindow.axaml.cs文件

    using Avalonia.Interactivity;
    using ChatGPT.Pages;
    
    namespace ChatGPT.Views;
    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
    
            var observer = Observer.Create(rect =>
            {
                if (ViewModel is null) return;
    
                ViewModel.SendChatViewModel.Height = (int)rect.Height;
                ViewModel.SendChatViewModel.Width = (int)rect.Width - 305;
                ViewModel.Height = (int)rect.Height;
                ViewModel.SendChatViewModel.ShowChatPanelHeight =
                    (int)rect.Height - ViewModel.SendChatViewModel.SendPanelHeight - 60;
            });
    
            this.GetObservable(BoundsProperty).Subscribe(observer);
    
            ChatShowView = this.Find(nameof(ChatShowView));
    
            ChatShowView.OnClick += view =>
            {
                ViewModel.SendChatViewModel.ChatShow = view;
            };
        }
    
        private MainViewModel ViewModel => DataContext as MainViewModel;
    
        private void Setting_OnClick(object? sender, RoutedEventArgs e)
        {
            var setting = new Setting
            {
                DataContext = ViewModel.SettingViewModel
            };
            setting.Show();
        }
    }
    

    提供部分代码 所有源码都是开源,链接防止最下面

    效果图

    SendChat.axaml.cs中提供了请求ChatGpt 3.5的实现

    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Json;
    using System.Threading.Tasks;
    using Avalonia.Controls.Notifications;
    using Avalonia.Controls.Primitives;
    using Avalonia.Input;
    using Avalonia.Interactivity;
    using ChatGPT.Model;
    using Notification = Avalonia.Controls.Notifications.Notification;
    
    namespace ChatGPT.Pages;
    
    public partial class SendChat : UserControl
    {
        private readonly HttpClient http;
    
        private WindowNotificationManager? _manager;
    
        public SendChat()
        {
            http = MainApp.GetService().CreateClient("chatGpt");
            InitializeComponent();
            DataContextChanged += async (sender, args) =>
            {
                if (DataContext is not SendChatViewModel model) return;
                if (model.ChatShow != null)
                {
                    var freeSql = MainApp.GetService();
                    try
                    {
                        var values = await freeSql.Select()
                            .Where(x => x.ChatShowKey == model.ChatShow.Key)
                            .OrderBy(x => x.CreatedTime)
                            .ToListAsync();
    
                        foreach (var value in values)
                        {
                            model.messages.Add(value);
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                        throw;
                    }
                }
                else
                {
                    model.ChatShowAction += async () =>
                    {
                        var freeSql = MainApp.GetService();
    
                        var values = await freeSql.Select()
                            .Where(x => x.Key == model.ChatShow.Key)
                            .OrderBy(x => x.CreatedTime)
                            .ToListAsync();
    
                        foreach (var value in values)
                        {
                            model.messages.Add(value);
                        }
                    };
                }
            };
        }
    
        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }
    
        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
        {
            base.OnAttachedToVisualTree(e);
            var topLevel = TopLevel.GetTopLevel(this);
            _manager = new WindowNotificationManager(topLevel) { MaxItems = 3 };
        }
    
        private void Close_OnClick(object? sender, RoutedEventArgs e)
        {
            var window = TopLevel.GetTopLevel(this) as Window;
            window.ShowInTaskbar = false;
            window.WindowState = WindowState.Minimized;
        }
    
        private SendChatViewModel ViewModel => DataContext as SendChatViewModel;
    
        private void Thumb_OnDragDelta(object? sender, VectorEventArgs e)
        {
            var thumb = (Thumb)sender;
            var wrapPanel = (WrapPanel)thumb.Parent;
            wrapPanel.Width += e.Vector.X;
            wrapPanel.Height += e.Vector.Y;
        }
    
        private void SendBorder_OnPointerEntered(object? sender, PointerEventArgs e)
        {
        }
    
        private async void SendMessage_OnClick(object? sender, RoutedEventArgs e)
        {
            await SendMessageAsync();
        }
    
        private void Minimize_OnClick(object? sender, RoutedEventArgs e)
        {
            var window = TopLevel.GetTopLevel(this) as Window;
            window.WindowState = WindowState.Minimized;
        }
    
        private void Maximize_OnClick(object? sender, RoutedEventArgs e)
        {
            var window = TopLevel.GetTopLevel(this) as Window;
            window.WindowState = window.WindowState switch
            {
                WindowState.Maximized => WindowState.Normal,
                WindowState.Normal => WindowState.Maximized,
                _ => window.WindowState
            };
        }
    
        private async void SendTextBox_OnKeyDown(object? sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                await SendMessageAsync();
            }
        }
    
        private async Task SendMessageAsync()
        {
            try
            {
                if (ViewModel?.ChatShow?.Key == null)
                {
                    _manager?.Show(new Notification("提示", "请先选择一个对话框!", NotificationType.Warning));
                    return;
                }
    
                // 获取当前程序集 assets图片
                // var uri = new Uri("avares://ChatGPT/Assets/avatar.png");
                // // 通过uri获取Stream
                // var bitmap = new Bitmap(AvaloniaLocator.Current.GetService().Open(uri));
    
                var model = new ChatMessage
                {
                    ChatShowKey = ViewModel.ChatShow.Key,
                    // Avatar = bitmap,
                    Title = "token",
                    Content = ViewModel.Message,
                    CreatedTime = DateTime.Now,
                    IsChatGPT = false
                };
    
                // 添加到消息列表
                ViewModel.messages.Add(model);
    
                // 清空输入框
                ViewModel.Message = string.Empty;
    
                // 获取消息记录用于AI联系上下文分析 来自Token的代码
                var message = ViewModel.messages
                    .OrderByDescending(x => x.CreatedTime) // 拿到最近的5条消息
                    .Take(5)
                    .OrderBy(x => x.CreatedTime) // 按时间排序
                    .Select(x => x.IsChatGPT
                        ? new
                        {
                            role = "assistant",
                            content = x.Content
                        }
                        : new
                        {
                            role = "user",
                            content = x.Content
                        }
                    )
                    .ToList();
    
                // 请求ChatGpt 3.5最新模型 来自Token的代码
                var responseMessage = await http.PostAsJsonAsync("https://api.openai.com/v1/chat/completions", new
                {
                    model = "gpt-3.5-turbo",
                    temperature = 0,
                    max_tokens = 2560,
                    user = "token",
                    messages = message
                });
    
                // 获取返回的消息 来自Token的代码
                var response = await responseMessage.Content.ReadFromJsonAsync();
    
                // 获取当前程序集 assets图片
                // uri = new Uri("avares://ChatGPT/Assets/chatgpt.ico");
    
                var chatGptMessage = new ChatMessage
                {
                    ChatShowKey = ViewModel.ChatShow.Key,
                    // Avatar = new Bitmap(AvaloniaLocator.Current.GetService().Open(uri)),
                    Title = "ChatGPT",
                    Content = response.choices[0].message.content,
                    IsChatGPT = true,
                    CreatedTime = DateTime.Now
                };
                // 添加到消息列表 来自Token的代码
                ViewModel.messages.Add(chatGptMessage);
    
                var freeSql = MainApp.GetService();
                await freeSql
                    .Insert(model)
                    .ExecuteAffrowsAsync();
    
                await freeSql
                    .Insert(chatGptMessage)
                    .ExecuteAffrowsAsync();
            }
            catch (Exception e)
            {
                // 异常处理 
                _manager?.Show(new Notification("提示", "在请求AI服务时出现错误!请联系管理员!", NotificationType.Error));
            }
        }
    }
    

    实现发送前需要将之前的最近五条数据得到跟随当前数据一块发送,为了让其ChatGpt可以联系上下文,这样回复的内容更准确,聊天的数据使用Sqlite本地存储,为了轻量使用ORM采用FreeSql,界面仿制微信的百分之八十的还原度,基本上一直,而且源码完全开源。

    来自token的分享

    GitHub开源地址: https://github.com/239573049/ChatGpt.Desktop

  • 相关阅读:
    MCE虚拟筛选化合物库
    android系统耗时关键字
    linux内的循环
    Python---练习:for循环 求1-100的和/所有偶数的和
    CesiumJS 中绘制大多边形
    目标检测前言,RCNN,Fast RCNN,Faster RCNN
    在 LangChain 尝试了 N 种可能后,我发现了分块的奥义!
    Java基础笔记
    软件成本评估的6个步骤
    Informix管理共享内存
  • 原文地址:https://www.cnblogs.com/hejiale010426/p/17180325.html