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; /// /// 当前AI的回复 /// [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(); } } /// /// 发送给AI的消息,包括上下文记录 /// private Message? CurrentRequest { get; set; } = Message.Empty; public ChatDialogueViewModel() { ChatHistory ??= new ObservableCollection(); } /// /// 用户输入 /// [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SendCommand))] private string userInput; private bool CanSend() { return !string.IsNullOrEmpty(UserInput); } public MarkdownPipeline Pipeline { get; set; } = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseColorCodeWpf().Build(); /// /// 历史聊天记录 /// public ObservableCollection 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 // { // { "需求", 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(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; //} }