Files
SzmediTools/Szmedi.RvKits/RvScript/ChatDialogueViewModel.cs
2025-12-23 21:37:02 +08:00

321 lines
12 KiB
C#
Raw 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
//using LangChain.Chains.LLM;
//using LangChain.Memory;
//using LangChain.Prompts;
using LangChain.Providers;
using LangChain.Providers.DeepSeek;
using LangChain.Providers.DeepSeek.Predefined;
using LangChain.Providers.OpenAI.Predefined;
using Markdig;
using Markdig.Syntax;
using Markdig.Wpf.ColorCode;
using tryAGI.OpenAI;
//using LangChain.Schema;
//using static LangChain.Chains.Chain;
namespace Szmedi.RvKits.RvScript;
public partial class ChatDialogueViewModel : ObservableObject
{
public DeepSeekConfiguration Configuration { get; set; } = new DeepSeekConfiguration()
{
Endpoint = "https://api.deepseek.com",
ChatSettings = new() { UseStreaming = true },
};
private ScrollViewer scrollViewer;
/// <summary>
/// 当前AI的回复
/// </summary>
[ObservableProperty]
private Message? currentRespone = Message.Ai(string.Empty);
[ObservableProperty]
private bool isCoderModel = true;
partial void OnIsCoderModelChanged(bool value)
{
NewSession();
}
[ObservableProperty]
private string apiKey = Properties.Settings.Default.APIKey;
partial void OnApiKeyChanged(string oldValue, string newValue)
{
if (!string.IsNullOrEmpty(newValue) && !string.IsNullOrWhiteSpace(newValue) && newValue.StartsWith("sk-"))
{
Properties.Settings.Default.APIKey = newValue;
Properties.Settings.Default.Save();
}
}
/// <summary>
/// 发送给AI的消息包括上下文记录
/// </summary>
private Message? CurrentRequest { get; set; } = Message.Empty;
public ChatDialogueViewModel()
{
ChatHistory ??= new ObservableCollection<Message?>();
}
/// <summary>
/// 用户输入
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
private string userInput;
private bool CanSend()
{
return !string.IsNullOrEmpty(UserInput);
}
public MarkdownPipeline Pipeline { get; set; } = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseColorCodeWpf().Build();
/// <summary>
/// 历史聊天记录
/// </summary>
public ObservableCollection<Message?> ChatHistory { get; set; }
[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";
}
else
{
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($"你是专业的Revit二次开发工程师不随意捏造事实能客观地回答用户的问题对于不确定或难以理解的问题需要用户补充说明的需要主动提出。你需要在{GlobalVariables.UIApplication.Application.VersionName}二次开发中使用已定义的uidoc、doc两个变量构造一个可以正确执行的C#代码块保证使用的RevitAPI的所有方法都能在{GlobalVariables.UIApplication.Application.VersionName}版本的API文档中找到在代码中添加关键的注释不需要Excute方法签名和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);
}
}
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");
//}
}
//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;
});
}
[RelayCommand]
private void NewSession()
{
ChatHistory?.Clear();
CurrentRequest = Message.Empty;
CurrentRespone = Message.Ai(string.Empty);
}
//[RelayCommand]
//private void SendCode()
//{
// ChatHistory?.Clear();
// CurrentRequest = Message.Empty;
//}
}