本次文章接前次写的“底子版”继续 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; } // 标记消息是否来自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
Resources/User.png
通过以上步调,我们成功创建了一个进阶版的DeepSeek本地客户端,具备了及时对话、消息区分和文本复制等功能。随着对WPF和DeepSeek的深入相识,您可以进一步扩展功能,比如增长更多的用户交互方式和优化UI计划。希望本文对您在WPF开发和DeepSeek应用方面有所资助!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |