前言
最佳deepseek火热网络,我也开发一款windows的电脑端,接入了deepseek,基本是复刻了网页端,还加入一些特色功能。
助力国内AI,发出自己的热量
说一下开发过程和内容的使用吧。
目录
一、介绍
- 目标:个人桌面AI助手,避免与专属API冲突,因为官网一些原因断网,自己接入API,确保能上deepseek。
先上图,确定是否需要使用:
- 软件是免费的,自己用自己的key或者别人的key,没有key可以联系我
二、具体工作
1.1、引用
dotnet8.0
<TargetFramework>net8.0-windows</TargetFramework>
PackageReference
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
<PackageReference Include="System.Speech" Version="8.0.0" />
<PackageReference Include="Spire.Doc" Version="12.8.0" />
Newtonsoft.Json 编译和反编译API信息
System.Drawing 绘制库,画界面主要
System.Speech 语音接入
Spire.Doc 文档使用
这几个引用根据自己需要添加
1.2、主界面
信息处理,分为及时信息和二次信息处理,这样能对文本信息进行个人需要处理
private async Task<ChatMessage> StreamResponseAsync(string apiKey, List<ChatMessage> chatHistory, string language, CancellationToken cancellationToken)
{
var deepseekService = new DeepseekService(currentUser, _printService);
var messages = chatHistory.Select(m => new ChatMessage
{
Role = m.Role,
MessageType = m.MessageType,
Content = m.Content
}).ToList();
try
{
var typingDelay = TimeSpan.FromMilliseconds(50);
var buffer = new StringBuilder();
var responseMessage = new ChatMessage
{
Role = "assistant",
MessageType = MessageType.Answer,
Content = string.Empty,
WrappedContent = string.Empty
};
// 创建响应消息但不立即添加到历史记录
var responseIndex = _displayHistory .Count;
// 实时处理流式响应 - deepseek输出开始
var charCount = 0;
var tempBuffer = new StringBuilder();
await foreach (var chunk in deepseekService.StreamChatResponseAsync(apiKey, messages, language).WithCancellation(cancellationToken))
{
if (string.IsNullOrEmpty(chunk) || _isStopping || cancellationToken.IsCancellationRequested)
{
// Immediately return if stopping
return new ChatMessage
{
Role = "assistant",
MessageType = MessageType.Answer,
Content = "对话已终止",
WrappedContent = "对话已终止"
};
}
tempBuffer.Append(chunk);
charCount += chunk.Length;
// 每20个字符更新一次显示
if (charCount >= 20)
{
buffer.Append(tempBuffer.ToString());
tempBuffer.Clear();
charCount = 0;
await Dispatcher.InvokeAsync(() => {
responseMessage.Content = buffer.ToString();
responseMessage.WrappedContent = WrapText(responseMessage.Content+"\n回答完成1",
ChatHistoryRichTextBox?.ActualWidth - 20 ?? ChatMessage.DefaultBorderWidth);
// 更新_curchat
_curchatMesg = responseMessage;
// 只在第一次更新时添加消息
if (_displayHistory .Count == responseIndex) {
_displayHistory .Add(responseMessage);
} else {
_displayHistory [responseIndex] = responseMessage;
}
UpdateChatHistoryDisplayList(_displayHistory);
});
await Task.Delay(typingDelay);
}
}
// 处理剩余不足20字符的内容
if (tempBuffer.Length > 0)
{
buffer.Append(tempBuffer.ToString());
await Dispatcher.InvokeAsync(() => {
responseMessage.Content = buffer.ToString() ;
responseMessage.WrappedContent = WrapText(responseMessage.Content+"\n回答完成2",
ChatHistoryRichTextBox?.ActualWidth - 20 ?? ChatMessage.DefaultBorderWidth);
// 更新_curchat
_curchatMesg = responseMessage;
_displayHistory [responseIndex] = responseMessage;
UpdateChatHistoryDisplayList(_displayHistory);
});
}
// deepseek输出完成 - 流式响应结束
// 进行最后的换行检查
await Dispatcher.InvokeAsync(() => {
responseMessage.WrappedContent = WrapText(responseMessage.Content+"\n回答完成3",
ChatHistoryRichTextBox?.ActualWidth - 20 ?? ChatMessage.DefaultBorderWidth);
// 更新_curchat
_curchatMesg = responseMessage;
_displayHistory [responseIndex] = responseMessage;
UpdateChatHistoryDisplayList(_displayHistory);
});
return responseMessage;
}
catch (Exception ex)
{
return new ChatMessage
{
Role = "assistant",
MessageType = MessageType.Answer,
Content = $"Error: {ex.Message}",
WrappedContent = $"Error: {ex.Message}"
};
}
finally
{
// 清空输入框
InputTextBox.Text = string.Empty;
}
}
1.3、主界面布局
<Window x:Class="AIzhushou.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AIzhushou.Converters"
Title="智能聊天助手" Height="820" Width="420" MinHeight="800" MinWidth="400"
WindowStartupLocation="Manual"
Background="#333333" ResizeMode="CanResizeWithGrip">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<local:EndMarkVisibilityConverter x:Key="EndMarkVisibilityConverter"/>
<local:MathConverter x:Key="MathConverter"/>
</ResourceDictionary>
</Window.Resources>
<Viewbox Stretch="Uniform">
<Grid Width="420" Height="760" Background="#333333">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="63*"/>
<ColumnDefinition Width="337*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Top Buttons -->
<DockPanel Grid.Row="0" Grid.Column="1" Margin="265,0,20,5" Width="88">
<Button x:Name="setting" Style="{StaticResource MainButtonStyle}"
Click="setting_Click" Height="40" Width="40" RenderTransformOrigin="0.5,0.5"
DockPanel.Dock="Right">
<Image Source="pack://application:,,,/AIzhushou;component/Img/icons8-home-page-50.png" Width="30" Height="30"/>
</Button>
</DockPanel>
<!-- Chat History Label -->
<Label Style="{StaticResource SectionLabelStyle}"
Content="聊天记录" Margin="22,0,0,0" Grid.ColumnSpan="2" VerticalAlignment="Center"/>
<!-- Chat History RichTextBox -->
<Border Grid.Row="1" Style="{StaticResource ChatBorderStyle}" HorizontalAlignment="Left"
Grid.ColumnSpan="2" Margin="15,0,0,10">
<Border.Resources>
<Style TargetType="ScrollViewer" BasedOn="{StaticResource CustomScrollViewerStyle}" />
</Border.Resources>
<RichTextBox x:Name="ChatHistoryRichTextBox" Style="{StaticResource ChatHistoryRichTextBoxStyle}"
Loaded="ChatHistoryRichTextBox_Loaded" Width="380" Margin="-10,0,0,0">
<RichTextBox.Resources>
<!-- 设置 Paragraph 的 Margin -->
<Style TargetType="Paragraph">
<Setter Property="Margin" Value="0"/>
</Style>
</RichTextBox.Resources>
</RichTextBox>
</Border>
<!-- New Conversation Button -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,10" Grid.Column="1">
<!--<Button x:Name="FreshButton" Style="{StaticResource FreshButtonStyle}"
Click="freshButton_Click" Height="36" Width="120">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Source="pack://application:,,,/AIzhushou;component/Img/icons8-refresh-96.png"
Width="20" Height="20" Margin="0,0,5,0"/>
<TextBlock Text="刷新刚才回答" VerticalAlignment="Center" Foreground="#4d6bfe"/>
</StackPanel>
</Button.Content>
</Button>-->
<Button x:Name="NewConversationButton" Style="{StaticResource NewConversationButtonStyle}"
Click="NewConversationButton_Click" Margin="90,0,5,0" Height="36" Width="120">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Source="pack://application:,,,/AIzhushou;component/Img/icons8-talk-64.png"
Width="20" Height="20" Margin="0,0,5,0"/>
<TextBlock Text="开启新对话" VerticalAlignment="Center" Foreground="#4d6bfe"/>
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
<!-- Input Label -->
<Label Grid.Row="3" Style="{StaticResource SectionLabelStyle}"
Content="输入消息" Margin="22,0,0,0" Grid.ColumnSpan="2" VerticalAlignment="Center"/>
<!-- Input Section -->
<Grid Grid.Row="4" HorizontalAlignment="Left" VerticalAlignment="Bottom"
Margin="22,0,0,-10" Width="378" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Input TextBox -->
<TextBox x:Name="InputTextBox" Grid.Column="0"
Style="{StaticResource InputTextBoxStyle}"
TextWrapping="Wrap" AcceptsReturn="True"
KeyDown="InputTextBox_KeyDown"
PreviewKeyDown="InputTextBox_KeyDown"/>
<!-- Send Button -->
<Border Grid.Column="1" Style="{StaticResource SendButtonBorderStyle}"
Margin="10,0,0,0">
<Button x:Name="SendButton" Style="{StaticResource SendButtonStyle}"
Content="发送" Click="SendButton_Click" IsEnabled="True"/>
</Border>
</Grid>
</Grid>
</Viewbox>
</Window>
1.4 、消息类
设计这个是为了对消息类对API的信息进行存入和处理
public enum MessageType
{
Question,
Answer
}
public class ChatMessage
{
public static double DefaultBorderWidth { get; set; } = 300;
public string Role { get; set; }
public string Content { get; set; }
public string WrappedContent { get; set; }
public double BorderWidth { get; set; } = DefaultBorderWidth;
public Brush BackgroundColor { get; set; }
public Brush TextColor { get; set; }
public DateTime Timestamp { get; set; }
public MessageType MessageType { get; set; }
public ChatMessage()
{
Role = string.Empty;
Content = string.Empty;
WrappedContent = string.Empty;
Timestamp = DateTime.Now;
BackgroundColor = Brushes.White;
TextColor = Brushes.Black;
}
public ChatMessage(string content, string wrappedContent, double borderWidth) : this()
{
Content = content;
WrappedContent = wrappedContent;
BorderWidth = borderWidth;
}
}
1.5 、设置
API KEY这里是必须填写的,不填写是无法进行问题的回答
这里是代码
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
// 更新用户信息
currentUser.Username = UsernameTextBox.Text;
currentUser.Password = PasswordBox.Password;
currentUser.AiUrl = AiUrlTextBox.Text;
currentUser.ModelUrl = ModelUrlTextBox.Text;
currentUser.ApiKey = ApiKeyTextBox.Text == "请输入deepseek的API keys" ?
string.Empty : ApiKeyTextBox.Text;
// 保存语言设置
var selectedLanguage = ((ComboBoxItem)LanguageComboBox.SelectedItem).Content.ToString();
string languageCode = selectedLanguage == "中文" ? "zh-CN" : "en-US";
AppSettings.Instance.DefaultLanguage = languageCode;
AppSettings.Instance.SaveLanguageSettings(languageCode);
// 保存复制选项设置
//AppSettings.Instance.CopyCurrent = CopyCurrentRadio.IsChecked == true;
AppSettings.Instance.Save();
// 保存到配置文件
string configPath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"AIzhushou",
"config.json");
// 确保目录存在
var directoryPath = System.IO.Path.GetDirectoryName(configPath);
if (string.IsNullOrEmpty(directoryPath))
{
throw new InvalidOperationException("无法确定配置文件的目录路径");
}
System.IO.Directory.CreateDirectory(directoryPath);
// 序列化保存
string json = Newtonsoft.Json.JsonConvert.SerializeObject(currentUser);
System.IO.File.WriteAllText(configPath, json);
MessageBox.Show("设置保存成功", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
this.Close();
}
三、运行图示
四、总结
- 实测了windows c#版本和python版本, c#版比python版本的性能更好,可能C#是微软亲儿子原因?
- 一定用要异步处理,没有什么特别需要
- AI里面,deepseek是真的强
- 如果需要提供协助,可以私信我
写着写着就这么多了,可能不是特别全,不介意费时就看看吧。有时间还会接着更新。