数据人与超自然意识 发表于 2025-3-4 13:28:32

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

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

   开发工具:VS 2015
开发情况:.Net 4.0
利用技能:WPF
本章内容:WPF实现一个进阶版的DeepSeek客户端。
结果图如下:
https://i-blog.csdnimg.cn/direct/6f6a2e6db8ee4cc0a95bd39e269f8c7c.png
实现的功能:
   1、及时吸收DeepSeek复兴的数据。
2、用户输入辨认和AI复兴辨认利用差别的头像。
3、能够复制笔墨。
2 搭建本地DeepSeek情况

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

Message.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace WpfApplication2
{
    public class Message : INotifyPropertyChanged
    {
      private string _content;
      public string Content
      {
            get { return _content; }
            set
            {
                if (_content != value)
                {
                  _content = value;
                  OnPropertyChanged(nameof(Content));// 通知UI更新
                }
            }
      }
      public bool IsAI { get; set; } // 标记消息是否来自AI
      public bool IsUser { get; set; } // 标记消息是否来自用户

      public event PropertyChangedEventHandler PropertyChanged;

      protected virtual void OnPropertyChanged(string propertyName)
      {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication2.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      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:local="clr-namespace:WpfApplication2"
      mc:Ignorable="d"
      Title="DeepSeek客户端" Height="680" Width="850">

    <Window.Resources>
      <!-- Boolean to Visibility Converter -->
      <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Window.DataContext>
      <local:ChatViewModel/>
    </Window.DataContext>


    <Grid>
      <Grid.RowDefinitions>
            <RowDefinition Height="8.5*"/>
            <RowDefinition Height="1.5*"/>
      </Grid.RowDefinitions>

      <!--第一个格子,AI对话格子-->
      <Grid Grid.Row="0" Grid.Column="0" Margin="0,15,0,0">
            <ListBox Name="listbox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Messages}" Margin="0,-20,0,-14">
                <ListBox.ItemTemplate>
                  <DataTemplate>
                        <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
                            <!-- AI消息 -->
                            <StackPanel Orientation="Horizontal" Visibility="{Binding IsAI, Converter={StaticResource BooleanToVisibilityConverter}}">
                              <Image Source="/Resources/Deepseek.png"   Width="40" Height="40" Margin="5"VerticalAlignment="Top" />
                              <!-- 使用TextBox代替TextBlock,并设置为只读 -->
                              <TextBox Text="{Binding Content}" FontFamily="Segoe UI" FontSize="16" Padding="5,10" TextWrapping="Wrap" MaxWidth="750" VerticalAlignment="Top" IsReadOnly="True" BorderBrush="Transparent" Background="Transparent" />
                            </StackPanel>
                            <!-- 用户消息 -->
                            <StackPanel Orientation="Horizontal" Visibility="{Binding IsUser, Converter={StaticResource BooleanToVisibilityConverter}}">
                              <Image Source="/Resources/User.png"Width="40" Height="40" Margin="5" VerticalAlignment="Top" />
                              <!-- 使用TextBox代替TextBlock,并设置为只读 -->
                              <TextBox Text="{Binding Content}" FontFamily="Segoe UI" FontSize="16" Padding="5,10" TextWrapping="Wrap" MaxWidth="750" VerticalAlignment="Top" IsReadOnly="True" BorderBrush="Transparent" Background="Transparent" />
                            </StackPanel>
                        </StackPanel>
                  </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
      </Grid>
      <!--第二个格子,用户输入框-->
      <Grid Grid.Row="1" Grid.Column="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3*" />
                <!-- 调整比例为3:1,更符合输入框和按钮的实际需求 -->
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>

            <!-- 输入信息框 -->
            <Grid Grid.Column="0" Margin="0,0,0,0">
                <!-- 统一化Margin值 -->
                <TextBox x:Name="InputTextBox"
               MaxWidth="540"
               Height="50"
                  VerticalAlignment="Bottom"
               KeyDown="InputTextBox_KeyDown"
               Margin="107,0,2.4,19.6"/>
                <!-- 移除内层Margin,使用Grid的Margin控制 -->
            </Grid>

            <!-- 发送按钮区域 -->
            <Grid Grid.Column="1" Margin="0,0,0,0">
                <!-- 添加右下Margin保持整体平衡 -->
                <!-- 发送按钮 -->
                <Button x:Name="SendButton"
                  Content="Send"
                  Width="70"
                  Height="40"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Bottom"
                  Background="#147bc6"
                  Foreground="White"
                  Click="SendButton_Click"
                  FontFamily="Arial Black"
                  FontSize="13"
                  Margin="6,0,0,23.6"/>

                <Button x:Name="SendButton1"
                  Content="new"
                  Width="30"
                  Height="30"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Bottom"
                  Background="#FFB6F5C2"
                  Foreground="#FF424234"
                  Click="SendButton_Click1"
                  FontFamily="Cambria"
                  Margin="93,0,0,49"/>
            </Grid>
      </Grid>

    </Grid>
</Window>

MainWindow.xaml.cs

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Net;
using System.Threading;


namespace WpfApplication2
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
      // 创建一个ChatViewModel对象来保存聊天历史
      private ChatViewModel _viewModel;
      // 用于存储对话的历史记录
      static StringBuilder conversationHistory = new StringBuilder();
      public MainWindow()
      {
            InitializeComponent();
            _viewModel = new ChatViewModel();
            DataContext = _viewModel;
      }


      /// <summary>
      /// 输入按钮框
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void InputTextBox_KeyDown(object sender, KeyEventArgs e)
      {
            // 用户输入
            string userInput = InputTextBox.Text;
            if (e.Key == Key.Enter)
            {
                // 异步方法需在同步上下文中调用(需手动处理)
                Task.Factory.StartNew(() =>
                {
                  // 调用同步的AIMain方法获取响应
                  RunAI(userInput);
                });
                clearText();
            }
      }

      /// <summary>
      /// 将最新的消息显示到最上面
      /// </summary>
      private void clearText()
      {
            // 设置最后一个消息为选中的项
            listbox.SelectedItem = _viewModel.Messages.LastOrDefault();
            // 滚动到选中的项(即最后一项)
            listbox.ScrollIntoView(listbox.SelectedItem);
      }
      /// <summary>
      /// 确认发送按钮
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void SendButton_Click(object sender, RoutedEventArgs e)
      {
            // 用户输入
            string userInput = InputTextBox.Text;
            // 异步方法需在同步上下文中调用(需手动处理)
            Task.Factory.StartNew(() =>
            {
                // 调用同步的AIMain方法获取响应
                RunAI(userInput);
            });
            clearText();
      }

      private CancellationTokenSource cancellationTokenSource;
      private CancellationToken cancellationToken;
      public void RunAI(string userInput)
      {
            // 如果输入不正确,不输出
            if (string.IsNullOrWhiteSpace(userInput))
                return;

            // 创建取消源
            cancellationTokenSource = new CancellationTokenSource();
            cancellationToken = cancellationTokenSource.Token;

            // 用户输入添加到历史对话记录
            conversationHistory.AppendLine($"用户: {userInput}");

            // 添加用户消息
            Dispatcher.Invoke((Action)(() =>
            {
                // 添加AI消息
                _viewModel.AddUserMessage(userInput);
            }));

            // 用户输入添加到历史对话记录
            var requestData = new
            {
                model = "deepseek-r1:1.5b",
                prompt = conversationHistory.ToString(),
                stream = true
            };

            string jsonContent = Newtonsoft.Json.JsonConvert.SerializeObject(requestData);
            byte[] byteArray = Encoding.UTF8.GetBytes(jsonContent);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:11434/api/generate");
            request.Method = "POST";
            request.ContentType = "application/json";
            request.ContentLength = byteArray.Length;

            try
            {
                using (Stream dataStream = request.GetRequestStream())
                {
                  dataStream.Write(byteArray, 0, byteArray.Length);
                }
            }
            catch
            {
                  MessageBox.Show("请本地配置DeepSeek,或者启动相关服务");
                  return;
            }


            try
            {
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                using (Stream responseStream = response.GetResponseStream())
                using (StreamReader reader = new StreamReader(responseStream))
                {
                  string line;
                  string line2 = "";
                  while ((line = reader.ReadLine()) != null)
                  {
                        // 检查取消标志
                        if (cancellationToken.IsCancellationRequested)
                        {
                            break; // 如果取消请求,退出读取流
                        }
                        if (!string.IsNullOrEmpty(line))
                        {
                            dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject(line);
                            if (result != null && result.response != null)
                            {
                              // 每次读取一行后,立即通过Dispatcher更新UI
                              string responseText = result.response;
                              // 去除所有多余的换行符(例如将每个换行符替换为空格)
                              responseText = responseText.Replace(Environment.NewLine, " ");
                              string surrt = RegexLine(responseText);
                              line2 += surrt;

                              Dispatcher.Invoke((Action)(() =>
                              {
                                    // 添加AI消息
                                    _viewModel.AddAIMessage(surrt);
                              }));
                            }
                        }
                  }
                  //添加历史对话
                  conversationHistory.AppendLine($"DeepSeek: {line2}");
                  line2 = "";
                }
            }
            catch (WebException ex)
            {
                MessageBox.Show("请求异常: " + ex.Message);
            }
            Dispatcher.Invoke((Action)(() =>
            {
                // 清空输入框
                InputTextBox.Text = "";
            }));
      }
      /// <summary>
      /// 处理DeepSeek返回的字符串
      /// </summary>
      /// <param name="line2"></param>
      /// <returns></returns>
      private string RegexLine(string line2)
      {
            // 使用正则表达式去掉 <think> 和 </think> 标签
            line2 = Regex.Replace(line2, @"<\/?think>", "\n");
            // 去掉开头的换行符
            line2 = line2.TrimStart('\r', '\n');
            return line2;
      }


      /// <summary>
      /// 开启新的对话
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void SendButton_Click1(object sender, RoutedEventArgs e)
      {
            // 取消流接收
            cancellationTokenSource?.Cancel();
            // 1清空 _viewModel 中的消息记录
            _viewModel.Messages.Clear();
            // 2清空输入框
            InputTextBox.Text = "";
            // 3清空历史记录
            conversationHistory.Clear();
      }

    }
}

ChatViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Text;
using System.ComponentModel;

namespace WpfApplication2
{
    public class ChatViewModel : INotifyPropertyChanged
    {
      private ObservableCollection<Message> _messages;
      public ObservableCollection<Message> Messages
      {
            get { return _messages; }
            set
            {
                _messages = value;
                OnPropertyChanged(nameof(Messages));
            }
      }

      public ChatViewModel()
      {
            Messages = new ObservableCollection<Message>();
      }


      // 添加用户消息
      public void AddUserMessage(string userInput)
      {
            Messages.Add(new Message { Content = userInput, IsUser = true, IsAI = false });
      }

      // 添加AI消息
      public void AddAIMessage(string newText)
      {
            // 检查是否已有消息,且最后一条消息是AI消息
            if (Messages.Any() && !Messages.Last().IsUser)
            {
                Messages.Last().Content += newText;// 追加流数据到最后一条消息
                OnPropertyChanged(nameof(Messages));// 通知UI更新
            }
            else
            {
                // 如果没有消息或最后一条消息是用户消息,则创建新消息
                Messages.Add(new Message { Content = newText, IsUser = false, IsAI = true });
            }
      }

      // 实现INotifyPropertyChanged接口
      public event PropertyChangedEventHandler PropertyChanged;

      protected virtual void OnPropertyChanged(string propertyName)
      {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
    }
}

BooleanToVisibilityConverter.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication2
{
    public class BooleanToVisibilityConverter : IValueConverter
    {
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
      {
            return value is bool && (bool)value ? Visibility.Visible : Visibility.Collapsed;
      }

      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
      {
            return value;
      }
    }
}

5 须要安装System.Net.Http库

Install-package System.Net.Http
6 干系图片如下

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: WPF创建DeepSeek本地自己的客户端-进阶版