Files
SzmediTools/Szmedi.AIScriptRunner/RvScript/ChatDialogueViewModel.cs

313 lines
11 KiB
C#
Raw Normal View History

2025-09-16 16:06:41 +08:00
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using LangChain.Providers;
using LangChain.Providers.DeepSeek;
using LangChain.Providers.DeepSeek.Predefined;
using Markdig;
namespace Szmedi.AIScriptRunner.RvScript;
public partial class ChatDialogueViewModel : ObservableObject
{
[ObservableProperty]
private string apiKey = AIScriptRunner.Properties.Settings.Default.APIKey;
/// <summary>
/// 当前AI的回复
/// </summary>
[ObservableProperty]
private Message? currentRespone = Message.Ai(string.Empty);
[ObservableProperty]
private bool isCoderModel = true;
private ScrollViewer? scrollViewer;
/// <summary>
/// 用户输入
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
private string? userInput;
public ChatDialogueViewModel() { ChatHistory ??= new ObservableCollection<Message?>(); }
private bool CanSend() { return !string.IsNullOrEmpty(UserInput); }
//partial void OnCurrentResponeChanged(Message? value)
//{
//}
//接收Delta
private void DeepseekLLM_DeltaReceived(object sender, ChatResponseDelta e)
{
Debug.WriteLine("-------------DeltaReceived-------------");
if (string.IsNullOrEmpty(e.Content))
{
return;
}
scrollViewer?.Dispatcher.Invoke(
() =>
{
if (ChatHistory.Contains(CurrentRespone))
{
Debug.WriteLine($"包含CurrentRespone对象");
ChatHistory.Remove(CurrentRespone);
}
Debug.WriteLine($"当前回复块:{e.Content}");
CurrentRespone += Message.Ai(e.Content);
if (!ChatHistory.Contains(CurrentRespone))
{
ChatHistory.Add(CurrentRespone);
}
//判断滚动条是否在底部
if (scrollViewer.VerticalOffset == scrollViewer.ExtentHeight - scrollViewer.ViewportHeight)
{
scrollViewer?.ScrollToEnd();
}
//ChatHistory.
//Respones.Content += e.Content;
});
}
private void DeepseekLLM_RequestSent(object sender, ChatRequest e)
{
Debug.WriteLine("-------------RequestSent-------------");
//Debug.WriteLine($"发送者{sender}");
//scrollViewer.Dispatcher.Invoke(() =>
//{
// ChatHistory.Add(CurrentRespone);
//});
}
//接收完毕
private void DeepseekLLM_ResponseReceived(object sender, ChatResponse e)
{
Debug.WriteLine("-------------ResponseReceived-------------");
scrollViewer?.Dispatcher.Invoke(
() =>
{
//Debug.WriteLine($"发送者:{sender};使用量:{e.Usage}");
CurrentRequest += e.LastMessage;
var code = GetCode(e.LastMessage.ToString());
if (!string.IsNullOrEmpty(code))
{
WeakReferenceMessenger.Default.Send(code, "RunScript");
}
CurrentRespone = Message.Ai(string.Empty);
Debug.WriteLine($"当前回复:{CurrentRespone}");
//最后一条完整的消息
//Debug.WriteLine($"{ChatHistory}");
//ChatHistory.Add(e.LastMessage);
//Respones.Content += e.Content;
});
}
private string GetCode(string markdownText)
{
//var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
//var RenderedMarkdown = Markdown.ToPlainText(markdownText, pipeline);
// 提取代码块(假设是 C# 代码)
var match = Regex.Match(markdownText, @"```csharp\s([\s\S]*?)\s```", RegexOptions.Multiline);
if (match.Success)
{
return match.Groups[1].Value;
}
return string.Empty;
//if (match.Success)
//{
// WeakReferenceMessenger.Default.Send(match.Groups[1].Value.Trim(), "RunScript");
//}
}
[RelayCommand]
private void NewSession()
{
ChatHistory?.Clear();
CurrentRequest = Message.Empty;
CurrentRespone = Message.Ai(string.Empty);
}
partial void OnApiKeyChanged(string? oldValue, string newValue)
{
if (!string.IsNullOrEmpty(newValue) && !string.IsNullOrWhiteSpace(newValue) && newValue.StartsWith("sk-"))
{
AIScriptRunner.Properties.Settings.Default.APIKey = newValue;
AIScriptRunner.Properties.Settings.Default.Save();
}
}
partial void OnIsCoderModelChanged(bool value) { NewSession(); }
[RelayCommand(CanExecute = nameof(CanSend), IncludeCancelCommand = true)]
private async Task SendAsync(object obj, CancellationToken cancellationToken)
{
//if (string.IsNullOrEmpty(Configuration.ApiKey) || string.IsNullOrEmpty(ApiKey))
//{
// Configuration.ApiKey = "sk-3a3126167f1343228b1a5745bcd0bf01";
//}
Configuration.ApiKey = Properties.Settings.Default.APIKey;
if (string.IsNullOrEmpty(Configuration.ApiKey))
{
var result = MessageBox.Show(
"请先在对话框标题处设置APIKey若无APIKey请在DeepSeek官网获取是否打开官网",
"提示",
MessageBoxButton.YesNo,
MessageBoxImage.Information);
if (result == MessageBoxResult.Yes)
{
Process.Start("https://platform.deepseek.com/");
}
return;
}
try
{
if (obj is not ScrollViewer scroll)
{
return;
}
scrollViewer = scroll;
//UserInput.Content=string.Empty;
#region Full
// //var llm = new OpenAiLatestFastChatModel(provider);
// var deepseekLLM = new DeepSeekChatModel(new DeepSeekProvider(config));
// //var embeddingModel = new TextEmbeddingV3SmallModel(provider);
// var prompt = new PromptTemplate(new PromptTemplateInput(
//template: "Revit二次开发中使用变量doc和uidoc两个变量构造一个保证可以执行的C#代码块添加相应注释不需要方法签名和using命名空间但使用时需要完整的命名空间。实现{需求}的功能", inputVariables: ["需求"]));
// var chain = new LlmChain(new LlmChainInput(deepseekLLM, prompt));
// var result = await chain.CallAsync(new ChainValues(new Dictionary<string, object>
// {
// { "需求", UserInput}
// })).ConfigureAwait(true);
// // The result is an object with a `text` property.
// Respones = result.Value["text"].ToString();
#endregion
if (IsCoderModel)
{
if (ChatHistory.Count == 0)
{
CurrentRequest += Message.Human(
$"{GlobalVariables.UIApplication.Application.VersionName}二次开发中使用已定义好的uidoc、doc两个变量构造一个正确执行的C#代码块保证RevitAPI方法签名准确添加关键的注释不需要方法签名和using命名空间但使用时需要完整的命名空间。需求是{UserInput}");
ChatHistory.Add(CurrentRequest);
}
else
{
ChatHistory.Add(Message.Human(UserInput));
CurrentRequest += Message.Human(UserInput);
}
UserInput = string.Empty;
var DeepseekCoder = new DeepSeekCoderModel(new DeepSeekProvider(Configuration));
DeepseekCoder.ResponseReceived += DeepseekLLM_ResponseReceived;
DeepseekCoder.DeltaReceived += DeepseekLLM_DeltaReceived;
DeepseekCoder.RequestSent += DeepseekLLM_RequestSent;
/*var result = */
await DeepseekCoder.GenerateAsync(CurrentRequest, cancellationToken: cancellationToken);
}
else
{
ChatHistory.Add(Message.Human(UserInput));
DeepSeekChatModel DeepseekLLM = new DeepSeekChatModel(new DeepSeekProvider(Configuration));
DeepseekLLM.ResponseReceived += DeepseekLLM_ResponseReceived;
DeepseekLLM.DeltaReceived += DeepseekLLM_DeltaReceived;
DeepseekLLM.RequestSent += DeepseekLLM_RequestSent;
CurrentRequest += Message.Human(UserInput);
UserInput = string.Empty;
/*var result = */
await DeepseekLLM.GenerateAsync(CurrentRequest, cancellationToken: cancellationToken);
}
// Since the LLMChain is a single-input, single-output chain, we can also call it with `run`.
// This takes in a string and returns the `text` property.
//var result2 = await chain.RunAsync("彩色长筒靴");
//Console.WriteLine(result2);
// var chatPrompt = ChatPromptTemplate.FromPromptMessages([
// SystemMessagePromptTemplate.FromTemplate(
// "You are a helpful assistant that translates {input_language} to {output_language}."),
// HumanMessagePromptTemplate.FromTemplate("{text}")
// ]);
// var chainB = new LlmChain(new LlmChainInput(deepseekLLM, chatPrompt)
// {
// Verbose = true
// });
// var resultB = await chainB.CallAsync(new ChainValues(new Dictionary<string, object>(3)
//{
// {"input_language", "English"},
// {"output_language", "French"},
// {"text", "I love programming"},
//}));
// Console.WriteLine(resultB.Value["text"]);
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// 发送给AI的消息包括上下文记录
/// </summary>
private Message? CurrentRequest { get; set; } = Message.Empty;
/// <summary>
/// 历史聊天记录
/// </summary>
public ObservableCollection<Message?> ChatHistory { get; set; }
public DeepSeekConfiguration Configuration
{
get;
set;
} = new DeepSeekConfiguration()
{
Endpoint = "https://api.deepseek.com",
ChatSettings = new() { UseStreaming = true }
};
public MarkdownPipeline Pipeline
{
get;
set;
} = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseColorCodeWpf().Build();
//[RelayCommand]
//private void SendCode()
//{
// ChatHistory?.Clear();
// CurrentRequest = Message.Empty;
//}
}