WPF创建DeepSeek本地自己的客户端-进阶版

打印 上一主题 下一主题

主题 907|帖子 907|积分 2721

本次文章接前次写的“底子版”继续  WPF快速创建DeepSeek本地自己的客户端-底子思绪版本
1 开发情况与工具

   开发工具:VS 2015
开发情况:.Net 4.0
利用技能:WPF
  本章内容:WPF实现一个进阶版的DeepSeek客户端。
结果图如下:

实现的功能:
   1、及时吸收DeepSeek复兴的数据。
2、用户输入辨认和AI复兴辨认利用差别的头像。
3、能够复制笔墨。
  2 搭建本地DeepSeek情况

我参考的是一下几个教程:
1、DeepSeek本地搭建部署+搭建知识库+智能体详细图文教程
2、【标题纪录】DeepSeek本地部署遇到标题
3、公司数据不泄漏,DeepSeek R1本地化部署+web端访问+个人知识库搭建与利用,喂饭级实操教程,老旧笔记本竟跑出企业级AI
4、【大语言模子】本地快速部署Ollama运行大语言模子详细流程
3 vs2015 创建WPF项目

Message.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Text;
  6. namespace WpfApplication2
  7. {
  8.     public class Message : INotifyPropertyChanged
  9.     {
  10.         private string _content;
  11.         public string Content
  12.         {
  13.             get { return _content; }
  14.             set
  15.             {
  16.                 if (_content != value)
  17.                 {
  18.                     _content = value;
  19.                     OnPropertyChanged(nameof(Content));  // 通知UI更新
  20.                 }
  21.             }
  22.         }
  23.         public bool IsAI { get; set; } // 标记消息是否来自AI
  24.         public bool IsUser { get; set; } // 标记消息是否来自用户
  25.         public event PropertyChangedEventHandler PropertyChanged;
  26.         protected virtual void OnPropertyChanged(string propertyName)
  27.         {
  28.             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  29.         }
  30.     }
  31. }
复制代码
MainWindow.xaml

  1. <Window x:Class="WpfApplication2.MainWindow"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6.         xmlns:local="clr-namespace:WpfApplication2"
  7.         mc:Ignorable="d"
  8.         Title="DeepSeek客户端" Height="680" Width="850">
  9.     <Window.Resources>
  10.         <!-- Boolean to Visibility Converter -->
  11.         <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
  12.     </Window.Resources>
  13.     <Window.DataContext>
  14.         <local:ChatViewModel/>
  15.     </Window.DataContext>
  16.     <Grid>
  17.         <Grid.RowDefinitions>
  18.             <RowDefinition Height="8.5*"/>
  19.             <RowDefinition Height="1.5*"/>
  20.         </Grid.RowDefinitions>
  21.         <!--第一个格子,AI对话格子-->
  22.         <Grid Grid.Row="0" Grid.Column="0" Margin="0,15,0,0">
  23.             <ListBox Name="listbox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Messages}" Margin="0,-20,0,-14">
  24.                 <ListBox.ItemTemplate>
  25.                     <DataTemplate>
  26.                         <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
  27.                             <!-- AI消息 -->
  28.                             <StackPanel Orientation="Horizontal" Visibility="{Binding IsAI, Converter={StaticResource BooleanToVisibilityConverter}}">
  29.                                 <Image Source="/Resources/Deepseek.png"   Width="40" Height="40" Margin="5"  VerticalAlignment="Top" />
  30.                                 <!-- 使用TextBox代替TextBlock,并设置为只读 -->
  31.                                 <TextBox Text="{Binding Content}" FontFamily="Segoe UI" FontSize="16" Padding="5,10" TextWrapping="Wrap" MaxWidth="750" VerticalAlignment="Top" IsReadOnly="True" BorderBrush="Transparent" Background="Transparent" />
  32.                             </StackPanel>
  33.                             <!-- 用户消息 -->
  34.                             <StackPanel Orientation="Horizontal" Visibility="{Binding IsUser, Converter={StaticResource BooleanToVisibilityConverter}}">
  35.                                 <Image Source="/Resources/User.png"  Width="40" Height="40" Margin="5" VerticalAlignment="Top" />
  36.                                 <!-- 使用TextBox代替TextBlock,并设置为只读 -->
  37.                                 <TextBox Text="{Binding Content}" FontFamily="Segoe UI" FontSize="16" Padding="5,10" TextWrapping="Wrap" MaxWidth="750" VerticalAlignment="Top" IsReadOnly="True" BorderBrush="Transparent" Background="Transparent" />
  38.                             </StackPanel>
  39.                         </StackPanel>
  40.                     </DataTemplate>
  41.                 </ListBox.ItemTemplate>
  42.             </ListBox>
  43.         </Grid>
  44.         <!--第二个格子,用户输入框-->
  45.         <Grid Grid.Row="1" Grid.Column="0">
  46.             <Grid.ColumnDefinitions>
  47.                 <ColumnDefinition Width="3*" />
  48.                 <!-- 调整比例为3:1,更符合输入框和按钮的实际需求 -->
  49.                 <ColumnDefinition Width="1*"/>
  50.             </Grid.ColumnDefinitions>
  51.             <!-- 输入信息框 -->
  52.             <Grid Grid.Column="0" Margin="0,0,0,0">
  53.                 <!-- 统一化Margin值 -->
  54.                 <TextBox x:Name="InputTextBox"
  55.                  MaxWidth="540"
  56.                  Height="50"  
  57.                     VerticalAlignment="Bottom"
  58.                  KeyDown="InputTextBox_KeyDown"
  59.                  Margin="107,0,2.4,19.6"/>
  60.                 <!-- 移除内层Margin,使用Grid的Margin控制 -->
  61.             </Grid>
  62.             <!-- 发送按钮区域 -->
  63.             <Grid Grid.Column="1" Margin="0,0,0,0">
  64.                 <!-- 添加右下Margin保持整体平衡 -->
  65.                 <!-- 发送按钮 -->
  66.                 <Button x:Name="SendButton"
  67.                     Content="Send"
  68.                     Width="70"
  69.                     Height="40"  
  70.                     HorizontalAlignment="Left"
  71.                     VerticalAlignment="Bottom"
  72.                     Background="#147bc6"
  73.                     Foreground="White"
  74.                     Click="SendButton_Click"
  75.                     FontFamily="Arial Black"
  76.                     FontSize="13"
  77.                     Margin="6,0,0,23.6"/>
  78.                 <Button x:Name="SendButton1"
  79.                     Content="new"
  80.                     Width="30"
  81.                     Height="30"
  82.                     HorizontalAlignment="Left"
  83.                     VerticalAlignment="Bottom"
  84.                     Background="#FFB6F5C2"
  85.                     Foreground="#FF424234"
  86.                     Click="SendButton_Click1"
  87.                     FontFamily="Cambria"
  88.                     Margin="93,0,0,49"/>
  89.             </Grid>
  90.         </Grid>
  91.     </Grid>
  92. </Window>
复制代码
MainWindow.xaml.cs

  1. using Newtonsoft.Json;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text.RegularExpressions;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net.Http;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. using System.Windows.Data;
  13. using System.Windows.Documents;
  14. using System.Windows.Input;
  15. using System.Windows.Media;
  16. using System.Windows.Media.Imaging;
  17. using System.Windows.Navigation;
  18. using System.Windows.Shapes;
  19. using System.Net;
  20. using System.Threading;
  21. namespace WpfApplication2
  22. {
  23.     /// <summary>
  24.     /// MainWindow.xaml 的交互逻辑
  25.     /// </summary>
  26.     public partial class MainWindow : Window
  27.     {
  28.         // 创建一个ChatViewModel对象来保存聊天历史
  29.         private ChatViewModel _viewModel;
  30.         // 用于存储对话的历史记录
  31.         static StringBuilder conversationHistory = new StringBuilder();
  32.         public MainWindow()
  33.         {
  34.             InitializeComponent();
  35.             _viewModel = new ChatViewModel();
  36.             DataContext = _viewModel;
  37.         }
  38.         /// <summary>
  39.         /// 输入按钮框
  40.         /// </summary>
  41.         /// <param name="sender"></param>
  42.         /// <param name="e"></param>
  43.         private void InputTextBox_KeyDown(object sender, KeyEventArgs e)
  44.         {
  45.             // 用户输入
  46.             string userInput = InputTextBox.Text;
  47.             if (e.Key == Key.Enter)
  48.             {
  49.                 // 异步方法需在同步上下文中调用(需手动处理)
  50.                 Task.Factory.StartNew(() =>
  51.                 {
  52.                     // 调用同步的AIMain方法获取响应
  53.                     RunAI(userInput);
  54.                 });
  55.                 clearText();
  56.             }
  57.         }
  58.         /// <summary>
  59.         /// 将最新的消息显示到最上面
  60.         /// </summary>
  61.         private void clearText()
  62.         {
  63.             // 设置最后一个消息为选中的项
  64.             listbox.SelectedItem = _viewModel.Messages.LastOrDefault();
  65.             // 滚动到选中的项(即最后一项)
  66.             listbox.ScrollIntoView(listbox.SelectedItem);
  67.         }
  68.         /// <summary>
  69.         /// 确认发送按钮
  70.         /// </summary>
  71.         /// <param name="sender"></param>
  72.         /// <param name="e"></param>
  73.         private void SendButton_Click(object sender, RoutedEventArgs e)
  74.         {
  75.             // 用户输入
  76.             string userInput = InputTextBox.Text;
  77.             // 异步方法需在同步上下文中调用(需手动处理)
  78.             Task.Factory.StartNew(() =>
  79.             {
  80.                 // 调用同步的AIMain方法获取响应
  81.                 RunAI(userInput);
  82.             });
  83.             clearText();
  84.         }
  85.         private CancellationTokenSource cancellationTokenSource;
  86.         private CancellationToken cancellationToken;
  87.         public void RunAI(string userInput)
  88.         {
  89.             // 如果输入不正确,不输出
  90.             if (string.IsNullOrWhiteSpace(userInput))
  91.                 return;
  92.             // 创建取消源
  93.             cancellationTokenSource = new CancellationTokenSource();
  94.             cancellationToken = cancellationTokenSource.Token;
  95.             // 用户输入添加到历史对话记录
  96.             conversationHistory.AppendLine($"用户: {userInput}");
  97.             // 添加用户消息
  98.             Dispatcher.Invoke((Action)(() =>
  99.             {
  100.                 // 添加AI消息
  101.                 _viewModel.AddUserMessage(userInput);
  102.             }));
  103.             // 用户输入添加到历史对话记录
  104.             var requestData = new
  105.             {
  106.                 model = "deepseek-r1:1.5b",
  107.                 prompt = conversationHistory.ToString(),
  108.                 stream = true
  109.             };
  110.             string jsonContent = Newtonsoft.Json.JsonConvert.SerializeObject(requestData);
  111.             byte[] byteArray = Encoding.UTF8.GetBytes(jsonContent);
  112.             HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:11434/api/generate");
  113.             request.Method = "POST";
  114.             request.ContentType = "application/json";
  115.             request.ContentLength = byteArray.Length;
  116.             try
  117.             {
  118.                 using (Stream dataStream = request.GetRequestStream())
  119.                 {
  120.                     dataStream.Write(byteArray, 0, byteArray.Length);
  121.                 }
  122.             }
  123.             catch
  124.             {
  125.                     MessageBox.Show("请本地配置DeepSeek,或者启动相关服务");
  126.                     return;
  127.             }
  128.             try
  129.             {
  130.                 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
  131.                 using (Stream responseStream = response.GetResponseStream())
  132.                 using (StreamReader reader = new StreamReader(responseStream))
  133.                 {
  134.                     string line;
  135.                     string line2 = "";
  136.                     while ((line = reader.ReadLine()) != null)
  137.                     {
  138.                         // 检查取消标志
  139.                         if (cancellationToken.IsCancellationRequested)
  140.                         {
  141.                             break; // 如果取消请求,退出读取流
  142.                         }
  143.                         if (!string.IsNullOrEmpty(line))
  144.                         {
  145.                             dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject(line);
  146.                             if (result != null && result.response != null)
  147.                             {
  148.                                 // 每次读取一行后,立即通过Dispatcher更新UI
  149.                                 string responseText = result.response;
  150.                                 // 去除所有多余的换行符(例如将每个换行符替换为空格)
  151.                                 responseText = responseText.Replace(Environment.NewLine, " ");
  152.                                 string surrt = RegexLine(responseText);
  153.                                 line2 += surrt;
  154.                                 Dispatcher.Invoke((Action)(() =>
  155.                                 {
  156.                                     // 添加AI消息
  157.                                     _viewModel.AddAIMessage(surrt);
  158.                                 }));
  159.                             }
  160.                         }
  161.                     }
  162.                     //添加历史对话
  163.                     conversationHistory.AppendLine($"DeepSeek: {line2}");
  164.                     line2 = "";
  165.                 }
  166.             }
  167.             catch (WebException ex)
  168.             {
  169.                 MessageBox.Show("请求异常: " + ex.Message);
  170.             }
  171.             Dispatcher.Invoke((Action)(() =>
  172.             {
  173.                 // 清空输入框
  174.                 InputTextBox.Text = "";
  175.             }));
  176.         }
  177.         /// <summary>
  178.         /// 处理DeepSeek返回的字符串
  179.         /// </summary>
  180.         /// <param name="line2"></param>
  181.         /// <returns></returns>
  182.         private string RegexLine(string line2)
  183.         {
  184.             // 使用正则表达式去掉 <think> 和 </think> 标签
  185.             line2 = Regex.Replace(line2, @"<\/?think>", "\n");
  186.             // 去掉开头的换行符
  187.             line2 = line2.TrimStart('\r', '\n');
  188.             return line2;
  189.         }
  190.         /// <summary>
  191.         /// 开启新的对话
  192.         /// </summary>
  193.         /// <param name="sender"></param>
  194.         /// <param name="e"></param>
  195.         private void SendButton_Click1(object sender, RoutedEventArgs e)
  196.         {
  197.             // 取消流接收
  198.             cancellationTokenSource?.Cancel();
  199.             // 1清空 _viewModel 中的消息记录
  200.             _viewModel.Messages.Clear();
  201.             // 2清空输入框
  202.             InputTextBox.Text = "";
  203.             // 3清空历史记录
  204.             conversationHistory.Clear();
  205.         }
  206.     }
  207. }
复制代码
ChatViewModel.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Collections.ObjectModel;
  5. using System.Text;
  6. using System.ComponentModel;
  7. namespace WpfApplication2
  8. {
  9.     public class ChatViewModel : INotifyPropertyChanged
  10.     {
  11.         private ObservableCollection<Message> _messages;
  12.         public ObservableCollection<Message> Messages
  13.         {
  14.             get { return _messages; }
  15.             set
  16.             {
  17.                 _messages = value;
  18.                 OnPropertyChanged(nameof(Messages));
  19.             }
  20.         }
  21.         public ChatViewModel()
  22.         {
  23.             Messages = new ObservableCollection<Message>();
  24.         }
  25.         // 添加用户消息
  26.         public void AddUserMessage(string userInput)
  27.         {
  28.             Messages.Add(new Message { Content = userInput, IsUser = true, IsAI = false });
  29.         }
  30.         // 添加AI消息
  31.         public void AddAIMessage(string newText)
  32.         {
  33.             // 检查是否已有消息,且最后一条消息是AI消息
  34.             if (Messages.Any() && !Messages.Last().IsUser)
  35.             {
  36.                 Messages.Last().Content += newText;  // 追加流数据到最后一条消息
  37.                 OnPropertyChanged(nameof(Messages));  // 通知UI更新
  38.             }
  39.             else
  40.             {
  41.                 // 如果没有消息或最后一条消息是用户消息,则创建新消息
  42.                 Messages.Add(new Message { Content = newText, IsUser = false, IsAI = true });
  43.             }
  44.         }
  45.         // 实现INotifyPropertyChanged接口
  46.         public event PropertyChangedEventHandler PropertyChanged;
  47.         protected virtual void OnPropertyChanged(string propertyName)
  48.         {
  49.             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  50.         }
  51.     }
  52. }
复制代码
BooleanToVisibilityConverter.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Windows;
  7. using System.Windows.Data;
  8. namespace WpfApplication2
  9. {
  10.     public class BooleanToVisibilityConverter : IValueConverter
  11.     {
  12.         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  13.         {
  14.             return value is bool && (bool)value ? Visibility.Visible : Visibility.Collapsed;
  15.         }
  16.         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  17.         {
  18.             return value;
  19.         }
  20.     }
  21. }
复制代码
5 须要安装System.Net.Http库

  1. Install-package System.Net.Http
复制代码
6 干系图片如下

Resources/Deepseek.png

Resources/User.png

   通过以上步调,我们成功创建了一个进阶版的DeepSeek本地客户端,具备了及时对话、消息区分和文本复制等功能。随着对WPF和DeepSeek的深入相识,您可以进一步扩展功能,比如增长更多的用户交互方式和优化UI计划。希望本文对您在WPF开发和DeepSeek应用方面有所资助!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

数据人与超自然意识

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表