经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 大数据/云/AI » 人工智能基础 » 查看文章
跨平台`ChatGpt` 客户端
来源:cnblogs  作者:tokengo  时间:2023/3/6 9:15:20  对本文有异议

跨平台ChatGpt 客户端

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

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

添加项目需要使用的Nuget

  1. <ItemGroup>
  2. <PackageReference Include="Avalonia" Version="11.0.0-preview5" />
  3. <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview5" />
  4. <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview5" />
  5. <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
  6. <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview5" />
  7. <PackageReference Include="FreeSql.Provider.Sqlite" Version="3.2.690" />
  8. <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
  9. <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
  10. <PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
  11. <PackageReference Include="Avalonia.Svg.Skia" Version="11.0.0-preview5" />
  12. </ItemGroup>

ViewLocator.cs代码修改

  1. using System;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Templates;
  4. using ChatGPT.ViewModels;
  5. namespace ChatGPT;
  6. public class ViewLocator : IDataTemplate
  7. {
  8. public Control? Build(object? data)
  9. {
  10. if (data is null)
  11. return null;
  12. var name = data.GetType().FullName!.Replace("ViewModel", "View");
  13. var type = Type.GetType(name);
  14. if (type != null)
  15. {
  16. return (Control)Activator.CreateInstance(type)!;
  17. }
  18. return new TextBlock { Text = name };
  19. }
  20. public bool Match(object? data)
  21. {
  22. return data is ViewModelBase;
  23. }
  24. }

创建MainApp.cs文件

  1. using Microsoft.Extensions.DependencyInjection;
  2. namespace ChatGPT;
  3. public static class MainApp
  4. {
  5. private static IServiceProvider ServiceProvider;
  6. public static ServiceCollection CreateServiceCollection()
  7. {
  8. return new ServiceCollection();
  9. }
  10. public static IServiceProvider Build(this IServiceCollection services)
  11. {
  12. return ServiceProvider = services.BuildServiceProvider();
  13. }
  14. public static T GetService<T>()
  15. {
  16. if (ServiceProvider is null)
  17. {
  18. throw new ArgumentNullException(nameof(ServiceProvider));
  19. }
  20. return ServiceProvider.GetService<T>();
  21. }
  22. public static IEnumerable<T> GetServices<T>()
  23. {
  24. if (ServiceProvider is null)
  25. {
  26. throw new ArgumentNullException(nameof(ServiceProvider));
  27. }
  28. return ServiceProvider.GetServices<T>();
  29. }
  30. public static object? GetService(Type type)
  31. {
  32. if (ServiceProvider is null)
  33. {
  34. throw new ArgumentNullException(nameof(ServiceProvider));
  35. }
  36. return ServiceProvider.GetService(type);
  37. }
  38. }

创建GlobalUsing.cs文件 全局引用

  1. global using System.Reactive;
  2. global using Avalonia;
  3. global using Avalonia.Controls;
  4. global using ChatGPT.ViewModels;
  5. global using Avalonia;
  6. global using Avalonia.Controls.ApplicationLifetimes;
  7. global using Avalonia.Markup.Xaml;
  8. global using ChatGPT.ViewModels;
  9. global using ChatGPT.Views;
  10. global using System;
  11. global using System.Collections.Generic;
  12. global using ReactiveUI;

修改App.axaml代码文件

  1. <Application xmlns="https://github.com/avaloniaui"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3. xmlns:local="using:ChatGPT"
  4. xmlns:converter="clr-namespace:ChatGPT.Converter"
  5. RequestedThemeVariant="Light"
  6. x:Class="ChatGPT.App">
  7. <Application.Resources>
  8. <converter:HeightConverter x:Key="HeightConverter" />
  9. </Application.Resources>
  10. <Application.DataTemplates>
  11. <local:ViewLocator/>
  12. </Application.DataTemplates>
  13. <Application.Styles>
  14. <FluentTheme DensityStyle="Compact"/>
  15. </Application.Styles>
  16. </Application>

修改App.axaml.cs代码文件

  1. using Avalonia.Platform;
  2. using Avalonia.Svg.Skia;
  3. using ChatGPT.Options;
  4. using Microsoft.Extensions.DependencyInjection;
  5. namespace ChatGPT;
  6. public partial class App : Application
  7. {
  8. public override void Initialize()
  9. {
  10. GC.KeepAlive(typeof(SvgImageExtension).Assembly);
  11. GC.KeepAlive(typeof(Avalonia.Svg.Skia.Svg).Assembly);
  12. var services = MainApp.CreateServiceCollection();
  13. services.AddHttpClient("chatGpt")
  14. .ConfigureHttpClient(options =>
  15. {
  16. var chatGptOptions = MainApp.GetService<ChatGptOptions>();
  17. if (!string.IsNullOrWhiteSpace(chatGptOptions?.Token))
  18. {
  19. options.DefaultRequestHeaders.Add("Authorization",
  20. "Bearer " + chatGptOptions?.Token.TrimStart().TrimEnd());
  21. }
  22. });
  23. services.AddSingleton<ChatGptOptions>(ChatGptOptions.NewChatGptOptions());
  24. services.AddSingleton(new FreeSql.FreeSqlBuilder()
  25. .UseConnectionString(FreeSql.DataType.Sqlite,
  26. "Data Source=chatGpt.db;Pooling=true;Min Pool Size=1")
  27. .UseAutoSyncStructure(true) //自动同步实体结构到数据库
  28. .Build());
  29. services.Build();
  30. AvaloniaXamlLoader.Load(this);
  31. }
  32. public override void OnFrameworkInitializationCompleted()
  33. {
  34. if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  35. {
  36. desktop.MainWindow = new MainWindow
  37. {
  38. DataContext = new MainViewModel()
  39. };
  40. }
  41. var notifyIcon = new TrayIcon();
  42. notifyIcon.Menu ??= new NativeMenu();
  43. notifyIcon.ToolTipText = "ChatGPT";
  44. var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
  45. notifyIcon.Icon = new WindowIcon(assets.Open(new Uri("avares://ChatGPT/Assets/chatgpt.ico")));
  46. var exit = new NativeMenuItem()
  47. {
  48. Header = "退出ChatGPT"
  49. };
  50. exit.Click += (sender, args) => Environment.Exit(0);
  51. notifyIcon.Menu.Add(exit);
  52. base.OnFrameworkInitializationCompleted();
  53. }
  54. }

修改MainWindow.axaml文件

  1. <Window xmlns="https://github.com/avaloniaui"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5. xmlns:viewModels="clr-namespace:ChatGPT.ViewModels"
  6. xmlns:pages="clr-namespace:ChatGPT.Pages"
  7. mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
  8. x:Class="ChatGPT.Views.MainWindow"
  9. ExtendClientAreaToDecorationsHint="True"
  10. ExtendClientAreaChromeHints="NoChrome"
  11. ExtendClientAreaTitleBarHeightHint="-1"
  12. Height="{Binding Height}"
  13. MinHeight="500"
  14. MinWidth="800"
  15. Width="1060"
  16. Name="Main">
  17. <Design.DataContext>
  18. <viewModels:MainViewModel />
  19. </Design.DataContext>
  20. <StackPanel Name="StackPanel" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
  21. <WrapPanel Name="WrapPanel" VerticalAlignment="Stretch" Height="{Binding ElementName=StackPanel, Path=Height}">
  22. <StackPanel MaxWidth="55" Width="55">
  23. <DockPanel Background="#2E2E2E" Height="{Binding Height}">
  24. <StackPanel DockPanel.Dock="Top">
  25. <StackPanel Margin="0,32,0,0"></StackPanel>
  26. <StackPanel Margin="8">
  27. <Image Source="/Assets/avatar.png"></Image>
  28. </StackPanel>
  29. <StackPanel Name="ChatStackPanel" Margin="15">
  30. <Image Source="/Assets/chat-1.png"></Image>
  31. </StackPanel>
  32. </StackPanel>
  33. <StackPanel Margin="5" VerticalAlignment="Bottom" DockPanel.Dock="Bottom">
  34. <StackPanel VerticalAlignment="Bottom" Name="FunctionStackPanel">
  35. <Menu HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
  36. <MenuItem HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
  37. <MenuItem.Header>
  38. <Image Margin="5" Height="20" Width="20" Source="/Assets/function.png"></Image>
  39. </MenuItem.Header>
  40. <MenuItem Click="Setting_OnClick" Name="Setting" Header="设置" />
  41. </MenuItem>
  42. </Menu>
  43. </StackPanel>
  44. </StackPanel>
  45. </DockPanel>
  46. </StackPanel>
  47. <Border Width="250" MaxWidth="250" BorderBrush="#D3D3D3" BorderThickness="0,0,1,0">
  48. <StackPanel>
  49. <pages:ChatShowView Name="ChatShowView"/>
  50. </StackPanel>
  51. </Border>
  52. <StackPanel>
  53. <StackPanel Height="{Binding Height}" HorizontalAlignment="Center" VerticalAlignment="Center">
  54. <pages:SendChat DataContext="{Binding SendChatViewModel}"></pages:SendChat>
  55. </StackPanel>
  56. </StackPanel>
  57. </WrapPanel>
  58. </StackPanel>
  59. </Window>

修改MainWindow.axaml.cs文件

  1. using Avalonia.Interactivity;
  2. using ChatGPT.Pages;
  3. namespace ChatGPT.Views;
  4. public partial class MainWindow : Window
  5. {
  6. public MainWindow()
  7. {
  8. InitializeComponent();
  9. var observer = Observer.Create<Rect>(rect =>
  10. {
  11. if (ViewModel is null) return;
  12. ViewModel.SendChatViewModel.Height = (int)rect.Height;
  13. ViewModel.SendChatViewModel.Width = (int)rect.Width - 305;
  14. ViewModel.Height = (int)rect.Height;
  15. ViewModel.SendChatViewModel.ShowChatPanelHeight =
  16. (int)rect.Height - ViewModel.SendChatViewModel.SendPanelHeight - 60;
  17. });
  18. this.GetObservable(BoundsProperty).Subscribe(observer);
  19. ChatShowView = this.Find<ChatShowView>(nameof(ChatShowView));
  20. ChatShowView.OnClick += view =>
  21. {
  22. ViewModel.SendChatViewModel.ChatShow = view;
  23. };
  24. }
  25. private MainViewModel ViewModel => DataContext as MainViewModel;
  26. private void Setting_OnClick(object? sender, RoutedEventArgs e)
  27. {
  28. var setting = new Setting
  29. {
  30. DataContext = ViewModel.SettingViewModel
  31. };
  32. setting.Show();
  33. }
  34. }

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

效果图

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

  1. using System.Linq;
  2. using System.Net.Http;
  3. using System.Net.Http.Json;
  4. using System.Threading.Tasks;
  5. using Avalonia.Controls.Notifications;
  6. using Avalonia.Controls.Primitives;
  7. using Avalonia.Input;
  8. using Avalonia.Interactivity;
  9. using ChatGPT.Model;
  10. using Notification = Avalonia.Controls.Notifications.Notification;
  11. namespace ChatGPT.Pages;
  12. public partial class SendChat : UserControl
  13. {
  14. private readonly HttpClient http;
  15. private WindowNotificationManager? _manager;
  16. public SendChat()
  17. {
  18. http = MainApp.GetService<IHttpClientFactory>().CreateClient("chatGpt");
  19. InitializeComponent();
  20. DataContextChanged += async (sender, args) =>
  21. {
  22. if (DataContext is not SendChatViewModel model) return;
  23. if (model.ChatShow != null)
  24. {
  25. var freeSql = MainApp.GetService<IFreeSql>();
  26. try
  27. {
  28. var values = await freeSql.Select<ChatMessage>()
  29. .Where(x => x.ChatShowKey == model.ChatShow.Key)
  30. .OrderBy(x => x.CreatedTime)
  31. .ToListAsync();
  32. foreach (var value in values)
  33. {
  34. model.messages.Add(value);
  35. }
  36. }
  37. catch (Exception e)
  38. {
  39. Console.WriteLine(e);
  40. throw;
  41. }
  42. }
  43. else
  44. {
  45. model.ChatShowAction += async () =>
  46. {
  47. var freeSql = MainApp.GetService<IFreeSql>();
  48. var values = await freeSql.Select<ChatMessage>()
  49. .Where(x => x.Key == model.ChatShow.Key)
  50. .OrderBy(x => x.CreatedTime)
  51. .ToListAsync();
  52. foreach (var value in values)
  53. {
  54. model.messages.Add(value);
  55. }
  56. };
  57. }
  58. };
  59. }
  60. private void InitializeComponent()
  61. {
  62. AvaloniaXamlLoader.Load(this);
  63. }
  64. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  65. {
  66. base.OnAttachedToVisualTree(e);
  67. var topLevel = TopLevel.GetTopLevel(this);
  68. _manager = new WindowNotificationManager(topLevel) { MaxItems = 3 };
  69. }
  70. private void Close_OnClick(object? sender, RoutedEventArgs e)
  71. {
  72. var window = TopLevel.GetTopLevel(this) as Window;
  73. window.ShowInTaskbar = false;
  74. window.WindowState = WindowState.Minimized;
  75. }
  76. private SendChatViewModel ViewModel => DataContext as SendChatViewModel;
  77. private void Thumb_OnDragDelta(object? sender, VectorEventArgs e)
  78. {
  79. var thumb = (Thumb)sender;
  80. var wrapPanel = (WrapPanel)thumb.Parent;
  81. wrapPanel.Width += e.Vector.X;
  82. wrapPanel.Height += e.Vector.Y;
  83. }
  84. private void SendBorder_OnPointerEntered(object? sender, PointerEventArgs e)
  85. {
  86. }
  87. private async void SendMessage_OnClick(object? sender, RoutedEventArgs e)
  88. {
  89. await SendMessageAsync();
  90. }
  91. private void Minimize_OnClick(object? sender, RoutedEventArgs e)
  92. {
  93. var window = TopLevel.GetTopLevel(this) as Window;
  94. window.WindowState = WindowState.Minimized;
  95. }
  96. private void Maximize_OnClick(object? sender, RoutedEventArgs e)
  97. {
  98. var window = TopLevel.GetTopLevel(this) as Window;
  99. window.WindowState = window.WindowState switch
  100. {
  101. WindowState.Maximized => WindowState.Normal,
  102. WindowState.Normal => WindowState.Maximized,
  103. _ => window.WindowState
  104. };
  105. }
  106. private async void SendTextBox_OnKeyDown(object? sender, KeyEventArgs e)
  107. {
  108. if (e.Key == Key.Enter)
  109. {
  110. await SendMessageAsync();
  111. }
  112. }
  113. private async Task SendMessageAsync()
  114. {
  115. try
  116. {
  117. if (ViewModel?.ChatShow?.Key == null)
  118. {
  119. _manager?.Show(new Notification("提示", "请先选择一个对话框!", NotificationType.Warning));
  120. return;
  121. }
  122. // 获取当前程序集 assets图片
  123. // var uri = new Uri("avares://ChatGPT/Assets/avatar.png");
  124. // // 通过uri获取Stream
  125. // var bitmap = new Bitmap(AvaloniaLocator.Current.GetService<IAssetLoader>().Open(uri));
  126. var model = new ChatMessage
  127. {
  128. ChatShowKey = ViewModel.ChatShow.Key,
  129. // Avatar = bitmap,
  130. Title = "token",
  131. Content = ViewModel.Message,
  132. CreatedTime = DateTime.Now,
  133. IsChatGPT = false
  134. };
  135. // 添加到消息列表
  136. ViewModel.messages.Add(model);
  137. // 清空输入框
  138. ViewModel.Message = string.Empty;
  139. // 获取消息记录用于AI联系上下文分析 来自Token的代码
  140. var message = ViewModel.messages
  141. .OrderByDescending(x => x.CreatedTime) // 拿到最近的5条消息
  142. .Take(5)
  143. .OrderBy(x => x.CreatedTime) // 按时间排序
  144. .Select(x => x.IsChatGPT
  145. ? new
  146. {
  147. role = "assistant",
  148. content = x.Content
  149. }
  150. : new
  151. {
  152. role = "user",
  153. content = x.Content
  154. }
  155. )
  156. .ToList();
  157. // 请求ChatGpt 3.5最新模型 来自Token的代码
  158. var responseMessage = await http.PostAsJsonAsync("https://api.openai.com/v1/chat/completions", new
  159. {
  160. model = "gpt-3.5-turbo",
  161. temperature = 0,
  162. max_tokens = 2560,
  163. user = "token",
  164. messages = message
  165. });
  166. // 获取返回的消息 来自Token的代码
  167. var response = await responseMessage.Content.ReadFromJsonAsync<GetChatGPTDto>();
  168. // 获取当前程序集 assets图片
  169. // uri = new Uri("avares://ChatGPT/Assets/chatgpt.ico");
  170. var chatGptMessage = new ChatMessage
  171. {
  172. ChatShowKey = ViewModel.ChatShow.Key,
  173. // Avatar = new Bitmap(AvaloniaLocator.Current.GetService<IAssetLoader>().Open(uri)),
  174. Title = "ChatGPT",
  175. Content = response.choices[0].message.content,
  176. IsChatGPT = true,
  177. CreatedTime = DateTime.Now
  178. };
  179. // 添加到消息列表 来自Token的代码
  180. ViewModel.messages.Add(chatGptMessage);
  181. var freeSql = MainApp.GetService<IFreeSql>();
  182. await freeSql
  183. .Insert(model)
  184. .ExecuteAffrowsAsync();
  185. await freeSql
  186. .Insert(chatGptMessage)
  187. .ExecuteAffrowsAsync();
  188. }
  189. catch (Exception e)
  190. {
  191. // 异常处理
  192. _manager?.Show(new Notification("提示", "在请求AI服务时出现错误!请联系管理员!", NotificationType.Error));
  193. }
  194. }
  195. }

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

来自token的分享

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

原文链接:https://www.cnblogs.com/hejiale010426/p/17180325.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号