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;
//}
}