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

devtools/2025/3/4 17:28:40/

本次文章接上次写的“基础版”继续 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

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; } // 标记消息是否来自AIpublic 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更新UIstring 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
在这里插入图片描述
Resources/User.png
在这里插入图片描述

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


http://www.ppmy.cn/devtools/164516.html

相关文章

【入门Web安全之前端学习的侧重点和针对性的建议】

入门Web安全之前端学习的侧重点和针对性的建议 一、HTML&#xff1a;理解攻击载荷的载体二、CSS&#xff1a;次要但需警惕点击劫持三、JavaScript&#xff1a;渗透测试的核心重点四、浏览器工具&#xff1a;渗透测试的实战武器五、学习建议与资源六、总结&#xff1a;渗透测试者…

2025全开源Java多语言跨境电商外贸商城/Tk/FB内嵌商城I商家入驻I批量下单I完美运行

商城程序介绍&#xff1a; 2025全新版UI 新增全球多站点选择 PC端&#xff1a;vueelementui 用户端使用&#xff1a;uniapp 管理端使用&#xff1a;vueelementui 后台服务使用&#xff1a;springbootmybatisplusmysql 商城功能介绍&#xff1a; 商城含21种语言 代理…

【Docker】Dify+ollama+deepseek(打造本地私有化大模型)

最近很流行私有化部署dp&#xff0c;之前已经尝试过ollamawebuideepseek本地化部署&#xff0c;但是体验感官上不是很多&#xff0c;特别卡顿。然后今天突然了解到Dify&#xff0c;也支持私有化部署大模型。而且似乎功能更加强大&#xff0c;那不得实操一下啊。 1.初识Dify D…

奖学金(acwing)c++

某小学最近得到了一笔赞助&#xff0c;打算拿出其中一部分为学习成绩优秀的前 55 名学生发奖学金。 期末&#xff0c;每个学生都有 33 门课的成绩:语文、数学、英语。 先按总分从高到低排序&#xff0c;如果两个同学总分相同&#xff0c;再按语文成绩从高到低排序&#xff0c…

Github 2025-02-28 Java开源项目日报 Top9

根据Github Trendings的统计,今日(2025-02-28统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目8非开发语言项目1《Hello 算法》:动画图解、一键运行的数据结构与算法教程 创建周期:476 天协议类型:OtherStar数量:63556 个Fork数…

yolov8训练模型、测试视频

yolov8先训练生成best.pt文件&#xff0c;用这个生成的模型进行视频的测试 因为本来用的代码生成的测试视频打不开&#xff0c;格式应该是损坏了&#xff0c;或者部分帧没有正常保存吧。 修改了一下代码&#xff0c;现状可以正常打开生成的视频了。 1、训练代码train.py im…

【Elasticsearch】修改数据流(Data Stream)

在Elasticsearch中&#xff0c;修改数据流&#xff08;Data Stream&#xff09;的操作主要包括更改映射&#xff08;mappings&#xff09;和设置&#xff08;settings&#xff09;。以下是关于如何修改数据流的详细步骤和方法&#xff1a; 1.修改数据流的映射 数据流的映射定…

win32汇编环境,窗口程序中使控件子类化的示例一

;运行效果 ;win32汇编环境,窗口程序中使编辑框控件子类化的示例一 ;窗口子类化&#xff0c;就是把某种控件&#xff0c;自已再打造一遍&#xff0c;加入自已的功能。比如弄个特殊形状的按钮&#xff0c;或只能输入特殊字符的编辑框 ;当然&#xff0c;一般来说&#xff0c;这都是…