313 lines
11 KiB
C#
313 lines
11 KiB
C#
|
|
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;
|
|||
|
|
//}
|
|||
|
|
}
|