Files
SzmediTools/Szmedi.AIScriptRunner/RvScript/ChatDialogueViewModel.cs
2025-09-16 16:06:41 +08:00

313 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
//}
}