添加项目

This commit is contained in:
GG Z
2026-01-01 10:02:59 +08:00
parent 1fd8d2ced7
commit 4df4ce1e6a
105 changed files with 4437 additions and 570 deletions

View File

@@ -0,0 +1,208 @@
<UserControl x:Class="WPFUI.Test.ChatDialogue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AduSkin="https://github.com/aduskin"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFUI.Test"
xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf"
xmlns:material="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:providers="clr-namespace:LangChain.Providers;assembly=LangChain.Providers.Abstractions"
Width="800"
Height="450"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:ChatDialogueViewModel}">
<!-- xmlns:md="clr-namespace:EleCho.MdViewer;assembly=EleCho.MdViewer"
xmlns:mu="clr-namespace:EleCho.MdViewer.Markup;assembly=EleCho.MdViewer" -->
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- <ResourceDictionary Source="/MarkdownStyleRes.xaml" /> -->
<!-- <mu:ControlsDictionary />
<mu:ThemeDictionary ColorMode="Dark" /> -->
</ResourceDictionary.MergedDictionaries>
<DrawingImage x:Key="deepseek">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
<GeometryDrawing Brush="#FF4D6BFE" Geometry="F1 M1024,1024z M0,0z M979.968,219.0336C969.8816,214.016 965.376,223.5392 959.3856,228.352 957.44,230.0416 955.5968,231.936 953.9072,233.8304 939.0592,249.856 921.856,260.1472 899.1232,258.9184 866.2016,257.024 837.9904,267.52 813.1072,292.864 807.8848,261.5808 790.1696,242.944 763.4432,230.9632 749.312,224.768 735.232,218.5216 725.4528,204.9024 718.4896,195.3792 716.5952,184.6272 713.216,174.08 711.1168,167.6288 708.9664,161.1776 701.5424,160 693.6576,158.8224 690.5344,165.4784 687.4112,171.008 675.0208,193.6896 670.208,219.0336 670.5152,244.3776 671.6416,301.4656 695.808,347.0848 743.8336,379.5968 749.312,383.4368 750.7456,387.0208 749.1072,392.4992 745.728,403.7632 741.9392,414.5152 738.56,425.728 736.4096,432.896 733.0816,434.5856 725.4016,431.4624A217.344,217.344,0,0,1,656.128,384.3584C621.9776,351.3856,591.104,314.8288,552.704,286.4128A511.0784,511.0784,0,0,0,525.1584,267.52C485.9904,229.2736 530.432,197.9904 540.7232,194.1504 551.4752,190.3616 544.512,176.9472 509.6448,177.2032 474.9824,177.2032 443.1872,188.928 402.5856,204.4416 396.5952,206.848 390.4,208.5376 384.1536,209.92A385.9968,385.9968,0,0,0,269.2608,205.8752C194.2016,214.2208 134.2464,249.856 90.0608,310.528 37.1712,383.488 24.4736,466.3296 39.7824,552.8576 55.808,643.8912 102.144,719.4112 173.6192,778.1888 247.5008,839.3728 332.8,869.2736 430.08,863.5392 489.1136,860.16 554.8032,852.2752 628.8896,789.4528 647.5264,798.7712 667.136,802.3552 699.8528,805.2224 724.9408,807.5776 749.1072,803.9936 767.744,800.2048 797.1328,793.9584 794.9824,766.976 784.4352,761.7024 698.6752,721.7664 717.568,738.048 700.3136,724.8896 744.0896,673.28 809.7792,619.52 835.328,445.5424 837.2736,431.7184 835.584,423.1168 835.328,411.8528 835.328,405.1968 836.7616,402.3296 844.4416,401.6128 865.9456,399.4624 886.9376,393.0112 906.0864,382.6688 961.792,352.3584 984.0128,302.4384 989.4912,242.432 990.208,233.3696 989.4912,223.7952 979.712,219.0336L979.9168,219.0336z M495.2576,757.9136C412.1088,692.3776 371.712,670.8736 354.9696,671.8976 339.456,672.8192 342.0672,690.4832 345.6512,702.208 349.2352,713.6768 354.048,721.5616 360.4992,731.5968 365.0048,738.2528 368.128,748.3392 355.8912,755.7632 329.1648,772.4544 282.5728,750.2336 280.4224,749.056 226.1504,717.0048 180.7872,674.9696 148.992,617.1136A401.7152,401.7152,0,0,1,97.3824,438.4256C96.6656,422.8608,101.1712,417.5872,116.2752,414.72A193.024,193.024,0,0,1,177.2032,413.0816C262.0416,425.472 334.3872,463.4624 394.9056,523.6736 429.5168,558.1312 455.5776,598.9376 482.6112,639.1296 511.2832,681.6256 542.1056,722.0736 581.2736,755.2512 595.0976,766.976 606.1568,775.8336 616.6016,782.2848 584.6016,785.8688 531.3536,786.5856 495.0016,757.6576L495.2576,757.9136z M535.1936,500.992A12.288,12.288,0,0,1,547.328,488.8064C554.0352,488.8064,559.5136,494.336,559.5136,500.992A12.288,12.288,0,0,1,547.1232,513.1776A12.032,12.032,0,0,1,534.9376,501.248L534.9376,500.992 535.1424,500.992z M658.944,564.7872A79.872,79.872,0,0,1,635.5456,571.2384A51.8656,51.8656,0,0,1,603.7504,561.2032C592.9984,552.1408,585.1136,546.8672,581.7344,530.8928A64.1024,64.1024,0,0,1,582.4512,507.4432C585.3184,494.336 582.2464,486.1952 572.928,478.5152 565.504,472.3712 555.9296,470.6304 545.4336,470.6304A18.432,18.432,0,0,1,535.3984,467.5584A10.24,10.24,0,0,1,530.0224,461.4144C529.5616,460.032,529.4592,458.5984,529.5616,457.216A10.3424,10.3424,0,0,1,530.8416,453.2224C532.0704,451.072 537.2928,445.7984 538.5216,444.8256 552.6016,436.736 569.088,439.3472 584.1408,445.5424 598.2208,451.2768 608.7168,461.824 624.0768,476.6208 639.5904,494.5408 642.4576,499.5584 651.3152,513.1776 658.2272,523.7248 664.6784,534.6816 668.9792,547.1232 671.5904,554.8032 668.2624,561.2032 658.944,565.0432L658.944,564.7872z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<local:RoleToDisplayConverter x:Key="RoleToDisplayConverter" />
<!-- 定义包含 DataTrigger 的 DataTemplate -->
<DataTemplate x:Key="DynamicTemplate" DataType="{x:Type providers:Message}">
<ContentControl x:Name="ContentControl" Content="{Binding}" />
<DataTemplate.Triggers>
<!-- 当 Role 为 Admin 时,使用 Admin 的模板 -->
<DataTrigger Value="{x:Static providers:MessageRole.Ai}" Binding="{Binding Role}">
<Setter TargetName="ContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<material:Card Margin="5"
HorizontalAlignment="{Binding Path=DataContext.Role,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource RoleToDisplayConverter}}"
Background="{Binding Path=DataContext.Role,
Mode=OneTime,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource RoleToDisplayConverter}}">
<StackPanel Margin="5" Orientation="Horizontal">
<Image Width="32"
Height="32"
Source="{StaticResource deepseek}"
Visibility="{Binding Path=DataContext.Role,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource RoleToDisplayConverter}}" />
<!-- <md:MarkdownViewer x:Name="Viewer" Content="{Binding Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> -->
<markdig:MarkdownViewer x:Name="Viewer"
Markdown="{Binding Content,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Pipeline="{Binding DataContext.Pipeline,
Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</StackPanel>
<!-- <mdxam:MarkdownScrollViewer HorizontalAlignment="Right" Markdown="{Binding Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalScrollBarVisibility="Auto" /> -->
</material:Card>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- 当 Role 为 User 时,使用 User 的模板 -->
<DataTrigger Value="{x:Static providers:MessageRole.Human}" Binding="{Binding Role}">
<Setter TargetName="ContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<material:Card Margin="5"
HorizontalAlignment="{Binding Path=DataContext.Role,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource RoleToDisplayConverter}}"
Background="{Binding Path=DataContext.Role,
Mode=OneTime,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource RoleToDisplayConverter}}">
<TextBox MaxWidth="300"
VerticalAlignment="Center"
Background="Transparent"
BorderBrush="{Binding Path=DataContext.Role,
Mode=OneTime,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource RoleToDisplayConverter}}"
BorderThickness="0"
FontSize="12"
IsReadOnly="True"
Style="{x:Null}"
Text="{Binding Content}"
TextWrapping="WrapWithOverflow" />
</material:Card>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<FrameworkElement.CommandBindings>
<CommandBinding Command="{x:Static markdig:Commands.Hyperlink}" Executed="OpenHyperlink" />
<CommandBinding Command="{x:Static markdig:Commands.Image}" Executed="ClickOnImage" />
</FrameworkElement.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- <Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions> -->
<ScrollViewer x:Name="ChatText"
Grid.Row="0"
VerticalScrollBarVisibility="Auto">
<ItemsControl d:ItemsSource="{d:SampleData ItemCount=5}"
ItemsSource="{Binding ChatHistory,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
ItemTemplate="{StaticResource DynamicTemplate}">
<!-- <ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Margin="5"
HorizontalAlignment="{Binding Path=DataContext.Role, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource RoleToDisplayConverter}}"
Background="{Binding Path=DataContext.Role, Mode=OneTime, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource RoleToDisplayConverter}}"
CornerRadius="10">
<StackPanel HorizontalAlignment="{Binding Path=DataContext.Role, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource RoleToDisplayConverter}}" Orientation="Horizontal">
<Image
Width="32"
Height="32"
Source="{StaticResource deepseek}"
Visibility="{Binding Path=DataContext.Role, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource RoleToDisplayConverter}}" />
<markdig:MarkdownViewer x:Name="Viewer" HorizontalAlignment="{Binding Path=DataContext.Role, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource RoleToDisplayConverter}}" Markdown="{Binding Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
-->
<!-- <mdxam:MarkdownScrollViewer HorizontalAlignment="Right" Markdown="{Binding Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalScrollBarVisibility="Auto" /> -->
<!-- </Border>
</DataTemplate>
</ItemsControl.ItemTemplate> -->
</ItemsControl>
</ScrollViewer>
<Border Grid.Row="1"
Background="Gray"
CornerRadius="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Margin="5"
Text="{Binding UserInput, UpdateSourceTrigger=PropertyChanged}">
<b:Interaction.Triggers>
<b:KeyTrigger Key="Enter">
<b:InvokeCommandAction Command="{Binding SendCommand}" CommandParameter="{Binding ElementName=ChatText}" />
</b:KeyTrigger>
</b:Interaction.Triggers>
</TextBox>
<!-- <Button x:Name="button1" Click="button1_Click" Content="单轮流式聊天" />
<Button x:Name="button2" Click="button2_Click" Content="单轮普通聊天" />
<Button x:Name="button3" Click="button3_Click" Content="多轮流式对话" />
<Button x:Name="button4" Click="button4_Click" Content="多轮普通聊天" /> -->
<Button Grid.Column="1"
Margin="5"
Command="{Binding SendCommand}"
CommandParameter="{Binding ElementName=ChatText}"
ToolTip="发送">
<material:PackIcon Kind="Send" />
</Button>
<Button Grid.Column="2"
Margin="5"
Command="{Binding SendCancelCommand}"
CommandParameter="{Binding ElementName=ChatText}"
ToolTip="发送">
<material:PackIcon Kind="Stop" />
</Button>
<Button Grid.Column="3"
Margin="5"
Command="{Binding NewSessionCommand}"
ToolTip="新对话">
<material:PackIcon Kind="Dialogue" />
</Button>
</Grid>
</Border>
<!-- <GroupBox Width="100" Header="分类" />
<StackPanel Grid.Column="1">
<TextBlock Text="A1" />
<TextBlock Text="A23" />
<Button Content="Button" />
<Button Content="Button" />
<Button Content="Button" />
</StackPanel> -->
</Grid>
</UserControl>

View File

@@ -0,0 +1,218 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace WPFUI.Test
{
/// <summary>
/// ChatDialogue.xaml 的交互逻辑
/// </summary>
public partial class ChatDialogue
{
private ChatDialogueViewModel viewModel = new ChatDialogueViewModel();
public ChatDialogue()
{
InitializeComponent();
this.DataContext = viewModel;
}
private void OpenHyperlink(object sender, ExecutedRoutedEventArgs e)
{
Process.Start(e.Parameter.ToString());
}
private void ClickOnImage(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
MessageBox.Show($"URL: {e.Parameter}");
}
//_deepSeekClient = new DeepSeekClient("sk-3a3126167f1343228b1a5745bcd0bf01");
///// <summary>
///// DeepSeek对象
///// </summary>
//private DeepSeekClient _deepSeekClient;
///// <summary>
///// 系统角色设定
///// </summary>
//private const string SysContent = "你是 DeepSeek AI 提供的人工智能助手你更擅长中文和英文的对话。你会为用户提供安全有帮助准确的回答。同时你会拒绝一切涉及恐怖主义种族歧视黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言";
///// <summary>
///// 单轮流式聊天
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//private async void button1_Click(object sender, RoutedEventArgs e)
//{
// try
// {
// richTextBox1.Document.Blocks.Clear();
// //------
// var chatReq = new ChatRequest
// {
// model = ChatModels.DeepseekChat,
// messages = new List<MessagesItem> {
// new MessagesItem { role = ChatRoles.System, content = SysContent },
// new MessagesItem { role = ChatRoles.User, content = textBox1.Text }
// },
// stream = true
// };
// // 订阅事件
// _deepSeekClient.MessageReceivedEventHandler -= OnMessageReceived;
// _deepSeekClient.ErrorEventHandler -= OnErrorMsg;
// _deepSeekClient.MessageReceivedEventHandler += OnMessageReceived;
// _deepSeekClient.ErrorEventHandler += OnErrorMsg;
// // 开始任务
// await _deepSeekClient.ChatStreamAsync(chatReq).ConfigureAwait(true);
// // 订阅SSE 消息事件
// void OnMessageReceived(object s, ChatResponse m)
// {
// //Console.WriteLine("聊天内容:" + ee);
// this.Dispatcher.Invoke(() =>
// {
// var msg = m.choices?.FirstOrDefault()?.delta.content;
// if (msg != null) richTextBox1.AppendText(msg);
// });
// }
// // 订阅SSE 错误事件
// void OnErrorMsg(object s, Exception ex)
// {
// MessageBox.Show(ex.Message);
// }
// }
// catch (Exception ex)
// {
// Console.WriteLine("流式聊天失败:" + ex.Message);
// }
//}
///// <summary>
///// 单轮普通聊天
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//private async void button2_Click(object sender, RoutedEventArgs e)
//{
// try
// {
// var chatReq = new ChatRequest
// {
// model = ChatModels.DeepseekChat,
// messages = new List<MessagesItem> {
// new MessagesItem { role = ChatRoles.System, content = SysContent },
// new MessagesItem { role = ChatRoles.User, content = textBox1.Text }
// }
// };
// var chatRes = await _deepSeekClient.ChatAsync(chatReq).ConfigureAwait(true);
// Console.WriteLine("聊天内容:" + chatRes.choices?.FirstOrDefault()?.message.content);
// Paragraph paragraph = new Paragraph();
// paragraph.Inlines.Add(new Run(chatRes.choices?.FirstOrDefault()?.message.content));
// richTextBox1.Document.Blocks.Add(paragraph);
// }
// catch (Exception ex)
// {
// Console.WriteLine("聊天失败:" + ex.Message);
// }
//}
///// <summary>
///// 多轮对话列表
///// </summary>
//private List<MessagesItem> HistoryList = new List<MessagesItem> { new MessagesItem { role = ChatRoles.System, content = SysContent } };
///// <summary>
///// 多轮流式对话
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//private async void button3_Click(object sender, RoutedEventArgs e)
//{
// try
// {
// richTextBox1.Document.Blocks.Clear();
// //------当前要说的内容
// HistoryList.Add(new MessagesItem { role = ChatRoles.User, content = textBox1.Text });
// //------
// var chatReq = new ChatRequest
// {
// model = ChatModels.DeepseekChat,
// messages = HistoryList,
// stream = true
// };
// // 订阅事件
// _deepSeekClient.MessageReceivedEventHandler -= OnMessageReceived;
// _deepSeekClient.ErrorEventHandler -= OnErrorMsg;
// _deepSeekClient.CloseedEventHandler -= OnCloseed;
// _deepSeekClient.MessageReceivedEventHandler += OnMessageReceived;
// _deepSeekClient.ErrorEventHandler += OnErrorMsg;
// _deepSeekClient.CloseedEventHandler += OnCloseed;
// // 开始任务
// await _deepSeekClient.ChatStreamAsync(chatReq).ConfigureAwait(true);
// // 局部函数,订阅SSE 消息事件
// void OnMessageReceived(object s, ChatResponse m)
// {
// //Console.WriteLine("聊天内容:" + ee);
// this.Dispatcher
// .Invoke(
// () =>
// {
// var msg = m.choices?.FirstOrDefault()?.delta.content;
// if (msg != null)
// {
// richTextBox1.AppendText(msg);
// }
// });
// }
// // 局部函数,订阅SSE 错误事件
// void OnErrorMsg(object s, Exception ex)
// {
// MessageBox.Show(ex.Message);
// }
// // 局部函数,订阅SSE 关闭事件
// void OnCloseed(object s, string ex)
// {
// HistoryList.Add(new MessagesItem { role = ChatRoles.Assistant, content = textBox1.Text });
// }
// }
// catch (Exception ex)
// {
// Console.WriteLine("流式聊天失败:" + ex.Message);
// }
//}
///// <summary>
///// 多轮普通聊天
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//private async void button4_Click(object sender, RoutedEventArgs e)
//{
// try
// {
// //------当前要说的内容
// HistoryList.Add(new MessagesItem { role = ChatRoles.User, content = textBox1.Text });
// //-------------------
// var chatReq = new ChatRequest
// {
// model = ChatModels.DeepseekChat,
// messages = HistoryList
// };
// var chatRes = await _deepSeekClient.ChatAsync(chatReq).ConfigureAwait(true);
// HistoryList.Add(new MessagesItem { role = ChatRoles.Assistant, content = textBox1.Text });
// Console.WriteLine("聊天内容:" + chatRes.choices?.FirstOrDefault()?.message.content);
// Paragraph paragraph = new Paragraph();
// paragraph.Inlines.Add(new Run(chatRes.choices?.FirstOrDefault()?.message.content));
// richTextBox1.Document.Blocks.Add(paragraph);
// }
// catch (Exception ex)
// {
// Console.WriteLine("聊天失败:" + ex.Message);
// }
//}
}
}

View File

@@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
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;
using LangChain.Providers.OpenAI.Predefined;
using LangChain.Schema;
using Markdig;
using Markdig.Wpf.ColorCode;
using tryAGI.OpenAI;
//using static LangChain.Chains.Chain;
namespace WPFUI.Test
{
public partial class ChatDialogueViewModel : ObservableObject
{
static readonly DeepSeekConfiguration config = new DeepSeekConfiguration()
{
ApiKey = "sk-3a3126167f1343228b1a5745bcd0bf01",
Endpoint = "https://api.deepseek.com",
ChatSettings = new() { UseStreaming = true }
};
private ScrollViewer scrollViewer;
/// <summary>
/// 当前AI的回复
/// </summary>
[ObservableProperty]
public partial Message? CurrentRespone { get; set; } = Message.Ai(string.Empty);
/// <summary>
/// 发送给AI的消息包括上下文记录
/// </summary>
private Message? CurrentRequest { get; set; } = Message.Empty;
public MarkdownPipeline Pipeline { get; set; } = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseColorCodeWpf().Build();
public ChatDialogueViewModel()
{
ChatHistory ??= new ObservableCollection<Message?>();
}
/// <summary>
/// 用户输入
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
public partial string UserInput { get; set; }
private bool CanSend()
{
return !string.IsNullOrEmpty(UserInput);
}
[RelayCommand]
private async void PromptChat()
{
try
{
var deepseekLLM = new DeepSeekChatModel(new DeepSeekProvider(config));
var prompt = new PromptTemplate(new PromptTemplateInput(
template: "Revit二次开发中使用变量doc和uidoc两个变量构造一个保证可以执行的C#代码块添加相应注释不需要方法签名和using命名空间但使用时需要完整的命名空间。实现{需求}的功能", inputVariables: ["需求"]));
deepseekLLM.RequestSent += DeepseekLLM_RequestSent;
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.
var respones = result.Value["text"].ToString();
}
catch (Exception ex)
{
}
}
/// <summary>
/// 历史聊天记录
/// </summary>
public ObservableCollection<Message?> ChatHistory { get; set; }
[RelayCommand(CanExecute = nameof(CanSend), IncludeCancelCommand = true)]
private async Task SendAsync(object obj, CancellationToken cancellationToken)
{
try
{
if (obj is ScrollViewer scroll)
{
scrollViewer = scroll;
}
ChatHistory.Add(Message.Human(UserInput));
//UserInput.Content=string.Empty;
#region DeepSeek
var deepseekLLM = new DeepSeekChatModel(new DeepSeekProvider(config));
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);
#endregion
// 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.
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private async static Task RunChainAsync()
{
try
{
var client = new OpenAiClient("sk-3a3126167f1343228b1a5745bcd0bf01");
OpenAiProvider provider = new OpenAiProvider(client);
var llm = new OpenAiLatestFastChatModel(provider);
var embeddingModel = new TextEmbeddingV3SmallModel(provider);
var prompt = new PromptTemplate(new PromptTemplateInput(
template: "Revit二次开发中使用变量doc和uidoc两个变量构造一个保证可以执行的C#代码块添加相应注释不需要方法签名和using命名空间但使用时需要完整的命名空间。实现{需求}的功能", inputVariables: ["需求"]));
var chain = new LlmChain(new LlmChainInput(llm, prompt));
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(llm, 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 (Exception ex)
{
}
}
private void DeepseekLLM_RequestSent(object sender, ChatRequest e)
{
Debug.WriteLine("-------------RequestSent-------------");
foreach (var mes in e.Messages)
{
Debug.WriteLine($"{mes}");
}
//Debug.WriteLine("-------------RequestSent-------------");
//Debug.WriteLine($"发送者{sender}");
//scrollViewer.Dispatcher.Invoke(() =>
//{
// ChatHistory.Add(CurrentRespone);
//});
}
//接收完毕
private void DeepseekLLM_ResponseReceived(object sender, ChatResponse e)
{
//Debug.WriteLine("-------------ResponseReceived-------------");
Application.Current.Dispatcher.Invoke(() =>
{
//Debug.WriteLine($"发送者:{sender};使用量:{e.Usage}");
CurrentRequest += e.LastMessage;
CurrentRespone = Message.Ai(string.Empty);
//最后一条完整的消息
//Debug.WriteLine($"{ChatHistory}");
//ChatHistory.Add(e.LastMessage);
//Respones.Content += e.Content;
});
}
//partial void OnCurrentResponeChanged(Message? value)
//{
//}
//接收Delta
private void DeepseekLLM_DeltaReceived(object sender, ChatResponseDelta e)
{
if (string.IsNullOrEmpty(e.Content))
{
return;
}
scrollViewer.Dispatcher.Invoke(() =>
{
ChatHistory.Remove(CurrentRespone);
//Debug.WriteLine("-------------DeltaReceived-------------");
Debug.WriteLine($"{e.Content}");
CurrentRespone += Message.Ai(e.Content);
ChatHistory.Add(CurrentRespone);
Task.Delay(1);
//ChatHistory.
//判断滚动条是否在底部
if (scrollViewer.VerticalOffset == scrollViewer.ExtentHeight - scrollViewer.ViewportHeight)
{
scrollViewer?.ScrollToEnd();
}
//Respones.Content += e.Content;
});
}
[RelayCommand]
private void NewSession()
{
ChatHistory?.Clear();
CurrentRequest = Message.Empty;
}
}
}

View File

@@ -0,0 +1,109 @@
using System.Text;
using System.Windows;
using System.Windows.Documents;
using ColorCode;
using ColorCode.Styling;
using ColorCode.Wpf;
using Markdig.Parsers;
using Markdig.Renderers;
using Markdig.Renderers.Wpf;
using Markdig.Syntax;
namespace Markdig.Wpf.ColorCode;
public class ColorCodeBlockRenderer : WpfObjectRenderer<CodeBlock>
{
private readonly CodeBlockRenderer _underlyingCodeBlockRenderer;
private readonly StyleDictionary _styleDictionary;
/// <summary>
/// Create a new <see cref="ColorCodeBlockRenderer"/> with the specified <paramref name="underlyingCodeBlockRenderer"/> and <paramref name="styleDictionary"/>.
/// </summary>
/// <param name="underlyingCodeBlockRenderer">The underlying CodeBlockRenderer to handle unsupported languages.</param>
/// <param name="styleDictionary">A StyleDictionary for custom styling.</param>
public ColorCodeBlockRenderer(CodeBlockRenderer underlyingCodeBlockRenderer, StyleDictionary styleDictionary)
{
_underlyingCodeBlockRenderer = underlyingCodeBlockRenderer;
_styleDictionary = styleDictionary;
}
/// <summary>
/// Writes the specified <paramref name="codeBlock"/> to the <paramref name="renderer"/>.
/// </summary>
/// <param name="renderer">The renderer.</param>
/// <param name="codeBlock">The code block to render.</param>
protected override void Write(WpfRenderer renderer, CodeBlock codeBlock)
{
if (codeBlock is not FencedCodeBlock fencedCodeBlock ||
codeBlock.Parser is not FencedCodeBlockParser fencedCodeBlockParser)
{
_underlyingCodeBlockRenderer.Write(renderer, codeBlock);
return;
}
var language = ExtractLanguage(fencedCodeBlock, fencedCodeBlockParser);
if (language is null)
{
_underlyingCodeBlockRenderer.Write(renderer, codeBlock);
return;
}
var code = ExtractCode(codeBlock);
//if (string.IsNullOrEmpty(code))
//{
// return;
//}
var formatter = new RichTextBoxFormatter(_styleDictionary);
var paragraph = new Paragraph();
paragraph.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.CodeBlockStyleKey);
formatter.FormatInlines(code, language, paragraph.Inlines);
renderer.WriteBlock(paragraph);
}
private static ILanguage? ExtractLanguage(IFencedBlock fencedCodeBlock, FencedCodeBlockParser parser)
{
var languageId = fencedCodeBlock.Info!.Replace(parser.InfoPrefix!, string.Empty);
return string.IsNullOrWhiteSpace(languageId) ? null : Languages.FindById(languageId);
}
private static string ExtractCode(LeafBlock leafBlock)
{
var code = new StringBuilder();
var lines = leafBlock.Lines.Lines;
//if (leafBlock.Line == 0)
//{
// return string.Empty;
//}
var totalLines = lines == null ? 0 : lines.Length;
for (var index = 0; index < totalLines; index++)
{
var line = lines[index];
var slice = line.Slice;
if (slice.Text == null)
{
continue;
}
var lineText = slice.Text.Substring(slice.Start, slice.Length);
if (index > 0)
{
code.AppendLine();
}
code.Append(lineText);
}
return code.ToString();
}
}

View File

@@ -0,0 +1,55 @@
using ColorCode.Styling;
using Markdig.Renderers;
using Markdig.Renderers.Wpf;
namespace Markdig.Wpf.ColorCode;
public class ColorCodeWpfExtension : IMarkdownExtension
{
private readonly StyleDictionary _styleDictionary;
/// <summary>
/// Creates a new <see cref="ColorCodeWpfExtension"/> with the specified <paramref name="styleDictionary"/>.
/// </summary>
/// <param name="styleDictionary">A dictionary indicating how to style the code.</param>
public ColorCodeWpfExtension(StyleDictionary styleDictionary) => _styleDictionary = styleDictionary;
/// <summary>
/// Sets up this extension for the specified pipeline.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
/// <summary>
/// Sets up this extension for the specified renderer.
/// </summary>
/// <param name="pipeline">The pipeline used to parse the document.</param>
/// <param name="renderer">The renderer.</param>
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not WpfRenderer wpfRenderer)
{
return;
}
var codeBlockRenderer = wpfRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>();
if (codeBlockRenderer != null)
{
wpfRenderer.ObjectRenderers.Remove(codeBlockRenderer);
}
else
{
codeBlockRenderer = new CodeBlockRenderer();
}
wpfRenderer.ObjectRenderers.AddIfNotAlready(
new ColorCodeBlockRenderer(
codeBlockRenderer,
_styleDictionary
)
);
}
}

View File

@@ -0,0 +1,21 @@
<Window
x:Class="WPFUI.Test.CustomChatWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFUI.Test"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="对话窗口"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<!--<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="设置" />-->
<local:ChatDialogue HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Grid>
</Window>

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
//using LangChain.Chains.LLM;
//using LangChain.DocumentLoaders;
//using LangChain.Prompts;
using LangChain.Providers;
using LangChain.Providers.DeepSeek;
using LangChain.Providers.DeepSeek.Predefined;
using LangChain.Providers.OpenAI;
using LangChain.Providers.OpenAI.Predefined;
//using LangChain.Schema;
namespace WPFUI.Test
{
/// <summary>
/// CustomChatWindow.xaml 的交互逻辑
/// </summary>
public partial class CustomChatWindow : Window
{
public CustomChatWindow()
{
InitializeComponent();
//ChatWithAI();
//ChatWithDeepSeek();
}
private async void ChatWithDeepSeek()
{
try
{
var Response = await DeepSeekHttpClient.Run();
}
catch (Exception)
{
throw;
}
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
//using Azure;
namespace WPFUI.Test
{
class DeepSeekHttpClient
{
private readonly HttpClient _client;
private const string BaseUrl = "https://api.deepseek.com";
private const string ApiUrl = "https://api.deepseek.com/chat/completions";
private const string ApiKey = "sk-3a3126167f1343228b1a5745bcd0bf01"; // 替换为你的实际API Key
public DeepSeekHttpClient()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}");
}
public async Task<string> GetResponseAsync(string prompt)
{
using var request = CreateRequest(prompt);
using var response = await _client.SendAsync(request);
var responseJson = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"API Error: {response.StatusCode}\n{responseJson}");
}
return ExtractResponseContent(responseJson);
}
private HttpRequestMessage CreateRequest(string prompt)
{
var body = new
{
model = "deepseek-chat",
messages = new[] { new { role = "user", content = prompt } },
temperature = 0.7
};
return new HttpRequestMessage(HttpMethod.Post, ApiUrl)
{
Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json")
};
}
private static string ExtractResponseContent(string json)
{
using var doc = JsonDocument.Parse(json);
return doc.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString() ?? string.Empty;
}
public static async Task<string> Run()
{
var client = new DeepSeekHttpClient();
return await client.GetResponseAsync("如何学习人工智能?");
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Windows.Media;
namespace ColorCode.Wpf.Common
{
public static class ExtensionMethods
{
public static SolidColorBrush GetSolidColorBrush(this string hex)
{
hex = hex.Replace("#", string.Empty);
byte a = 255;
int index = 0;
if (hex.Length == 8)
{
a = (byte)(Convert.ToUInt32(hex.Substring(index, 2), 16));
index += 2;
}
byte r = (byte)(Convert.ToUInt32(hex.Substring(index, 2), 16));
index += 2;
byte g = (byte)(Convert.ToUInt32(hex.Substring(index, 2), 16));
index += 2;
byte b = (byte)(Convert.ToUInt32(hex.Substring(index, 2), 16));
SolidColorBrush myBrush = new(Color.FromArgb(a, r, g, b));
return myBrush;
}
}
}

View File

@@ -0,0 +1,24 @@
using ColorCode.Styling;
namespace Markdig.Wpf.ColorCode;
/// <summary>
/// Extensions for adding ColorCode to the Markdig pipeline.
/// </summary>
public static class MarkdownPipelineBuilderExtension
{
/// <summary>
/// Use ColorCode to apply code colorization to markdown within the Markdig pipeline.
/// </summary>
/// <param name="pipeline">The pipeline the ColorCode extension is being added to.</param>
/// <param name="styleDictionary">An optional StyleDictionary for custom styling.</param>
/// <returns>The MarkdownPipelineBuilder with the added ColorCode extension.</returns>
public static MarkdownPipelineBuilder UseColorCodeWpf(
this MarkdownPipelineBuilder pipeline,
StyleDictionary? styleDictionary = null)
{
pipeline.Extensions.Add(new ColorCodeWpfExtension(styleDictionary ?? StyleDictionary.DefaultLight));
return pipeline;
}
}

View File

@@ -0,0 +1,101 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf">
<!-- Document styles -->
<Style x:Key="{x:Static markdig:Styles.DocumentStyleKey}" TargetType="{x:Type FlowDocument}">
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
<Setter Property="TextAlignment" Value="Left" />
<Setter Property="Foreground" Value="FloralWhite" />
<Setter Property="LineHeight" Value="5" />
</Style>
<Style TargetType="{x:Type List}">
<Setter Property="Margin" Value="40,0,0,0" />
<Setter Property="Padding" Value="0,0,0,0" />
</Style>
<Style x:Key="{x:Static markdig:Styles.CodeBlockStyleKey}" TargetType="{x:Type Paragraph}">
<Setter Property="Background" Value="#FF424242" />
<Setter Property="FontFamily" Value="Consolas, Lucida Sans Typewriter, Courier New" />
</Style>
<Style x:Key="{x:Static markdig:Styles.CodeStyleKey}" TargetType="{x:Type Run}">
<Setter Property="Background" Value="#FF424242" />
<Setter Property="FontFamily" Value="Consolas, Lucida Sans Typewriter, Courier New" />
</Style>
<Style x:Key="{x:Static markdig:Styles.Heading1StyleKey}" TargetType="{x:Type Paragraph}">
<Setter Property="FontSize" Value="42" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style x:Key="{x:Static markdig:Styles.Heading2StyleKey}" TargetType="{x:Type Paragraph}">
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style x:Key="{x:Static markdig:Styles.Heading3StyleKey}" TargetType="{x:Type Paragraph}">
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style x:Key="{x:Static markdig:Styles.Heading4StyleKey}" TargetType="{x:Type Paragraph}">
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Light" />
<Setter Property="TextDecorations" Value="Underline" />
</Style>
<Style x:Key="{x:Static markdig:Styles.Heading5StyleKey}" TargetType="{x:Type Paragraph}">
<!-- no changes -->
</Style>
<Style x:Key="{x:Static markdig:Styles.Heading6StyleKey}" TargetType="{x:Type Paragraph}">
<!-- no changes -->
</Style>
<Style x:Key="{x:Static markdig:Styles.HyperlinkStyleKey}" TargetType="{x:Type Hyperlink}">
<!-- no changes -->
</Style>
<Style x:Key="{x:Static markdig:Styles.ImageStyleKey}" TargetType="{x:Type Image}">
<Setter Property="MaxHeight" Value="{Binding RelativeSource={RelativeSource Self}, Path=Source.(BitmapSource.PixelHeight)}" />
<Setter Property="MaxWidth" Value="{Binding RelativeSource={RelativeSource Self}, Path=Source.(BitmapSource.PixelWidth)}" />
</Style>
<Style x:Key="{x:Static markdig:Styles.QuoteBlockStyleKey}" TargetType="{x:Type Section}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="4,0,0,0" />
<Setter Property="Padding" Value="16,0,0,0" />
</Style>
<Style x:Key="{x:Static markdig:Styles.TableStyleKey}" TargetType="{x:Type Table}">
<Setter Property="BorderBrush" Value="WhiteSmoke" />
<Setter Property="BorderThickness" Value="0,0,1,1" />
<Setter Property="CellSpacing" Value="0" />
</Style>
<Style x:Key="{x:Static markdig:Styles.TableCellStyleKey}" TargetType="{x:Type TableCell}">
<Setter Property="BorderBrush" Value="WhiteSmoke" />
<Setter Property="BorderThickness" Value="1,1,0,0" />
</Style>
<Style x:Key="{x:Static markdig:Styles.TableHeaderStyleKey}" TargetType="{x:Type TableRow}">
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style x:Key="{x:Static markdig:Styles.TaskListStyleKey}" TargetType="{x:Type CheckBox}">
<Setter Property="Margin" Value="0,0,0,-2" />
</Style>
<Style x:Key="{x:Static markdig:Styles.ThematicBreakStyleKey}" TargetType="{x:Type Line}">
<Setter Property="Stretch" Value="Fill" />
<Setter Property="Stroke" Value="WhiteSmoke" />
</Style>
<Style x:Key="{x:Static markdig:Styles.SubscriptStyleKey}" TargetType="{x:Type Span}">
<Setter Property="Typography.Variants" Value="Subscript" />
</Style>
<Style x:Key="{x:Static markdig:Styles.SuperscriptStyleKey}" TargetType="{x:Type Span}">
<Setter Property="Typography.Variants" Value="Superscript" />
</Style>
<Style x:Key="{x:Static markdig:Styles.StrikeThroughStyleKey}" TargetType="{x:Type Span}">
<Setter Property="TextBlock.TextDecorations" Value="Strikethrough" />
</Style>
<Style x:Key="{x:Static markdig:Styles.InsertedStyleKey}" TargetType="{x:Type Span}">
<Setter Property="TextBlock.TextDecorations" Value="Underline" />
</Style>
<Style x:Key="{x:Static markdig:Styles.MarkedStyleKey}" TargetType="{x:Type Span}">
<Setter Property="Background" Value="Yellow" />
</Style>
<!-- MarkdownViewer Template -->
<Style TargetType="{x:Type markdig:MarkdownViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type markdig:MarkdownViewer}">
<FlowDocumentScrollViewer Document="{TemplateBinding Document}" ScrollViewer.VerticalScrollBarVisibility="Auto" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,149 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using ColorCode.Common;
using ColorCode.Parsing;
using ColorCode.Styling;
using ColorCode.Wpf.Common;
namespace ColorCode.Wpf
{
/// <summary>
/// Creates a <see cref="RichTextBoxFormatter"/>, for rendering Syntax Highlighted code to a RichTextBlock.
/// </summary>
public class RichTextBoxFormatter : CodeColorizerBase
{
/// <summary>
/// Creates a <see cref="RichTextBoxFormatter"/>, for rendering Syntax Highlighted code to a RichTextBlock.
/// </summary>
/// <param name="style">The Custom styles to Apply to the formatted Code.</param>
/// <param name="languageParser">The language parser that the <see cref="RichTextBoxFormatter"/> instance will use for its lifetime.</param>
public RichTextBoxFormatter(StyleDictionary? style = null, ILanguageParser? languageParser = null) : base(style, languageParser)
{
}
/// <summary>
/// Adds Syntax Highlighted Source Code to the provided RichTextBlock.
/// </summary>
/// <param name="sourceCode">The source code to colorize.</param>
/// <param name="language">The language to use to colorize the source code.</param>
/// <param name="richText">The Control to add the Text to.</param>
public void FormatRichTextBox(string sourceCode, ILanguage language, RichTextBox richText)
{
var paragraph = new Paragraph();
richText.Document.Blocks.Add(paragraph);
FormatInlines(sourceCode, language, paragraph.Inlines);
}
/// <summary>
/// Adds Syntax Highlighted Source Code to the provided InlineCollection.
/// </summary>
/// <param name="sourceCode">The source code to colorize.</param>
/// <param name="language">The language to use to colorize the source code.</param>
/// <param name="inlineCollection">InlineCollection to add the Text to.</param>
public void FormatInlines(string sourceCode, ILanguage language, InlineCollection inlineCollection)
{
InlineCollection = inlineCollection;
languageParser.Parse(sourceCode, language, (parsedSourceCode, captures) => Write(parsedSourceCode, captures));
}
private InlineCollection? InlineCollection { get; set; }
protected override void Write(string parsedSourceCode, IList<Scope> scopes)
{
var styleInsertions = new List<TextInsertion>();
foreach (Scope scope in scopes)
GetStyleInsertionsForCapturedStyle(scope, styleInsertions);
styleInsertions.SortStable((x, y) => x.Index.CompareTo(y.Index));
int offset = 0;
Scope? previousScope = null;
foreach (var styleinsertion in styleInsertions)
{
var text = parsedSourceCode.Substring(offset, styleinsertion.Index - offset);
CreateSpan(text, previousScope);
if (!string.IsNullOrWhiteSpace(styleinsertion.Text))
{
CreateSpan(text, previousScope);
}
offset = styleinsertion.Index;
previousScope = styleinsertion.Scope;
}
var remaining = parsedSourceCode.Substring(offset);
// Ensures that those loose carriages don't run away!
if (remaining != "\r")
{
CreateSpan(remaining, null);
}
}
private void CreateSpan(string text, Scope? scope)
{
var span = new Span();
var run = new Run
{
Text = text
};
// Styles and writes the text to the span.
if (scope != null) StyleRun(run, scope);
span.Inlines.Add(run);
InlineCollection?.Add(span);
}
private void StyleRun(Run run, Scope scope)
{
string? foreground = null;
string? background = null;
bool italic = false;
bool bold = false;
if (Styles.Contains(scope.Name))
{
Styling.Style style = Styles[scope.Name];
foreground = style.Foreground;
background = style.Background;
italic = style.Italic;
bold = style.Bold;
}
if (!string.IsNullOrWhiteSpace(foreground))
run.Foreground = foreground.GetSolidColorBrush();
//Background isn't supported, but a workaround could be created.
if (italic)
run.FontStyle = FontStyles.Italic;
if (bold)
run.FontWeight = FontWeights.Bold;
}
private void GetStyleInsertionsForCapturedStyle(Scope scope, ICollection<TextInsertion> styleInsertions)
{
styleInsertions.Add(new TextInsertion
{
Index = scope.Index,
Scope = scope
});
foreach (Scope childScope in scope.Children)
GetStyleInsertionsForCapturedStyle(childScope, styleInsertions);
styleInsertions.Add(new TextInsertion
{
Index = scope.Index + scope.Length
});
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using LangChain.Providers;
namespace WPFUI.Test
{
internal class RoleToDisplayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is MessageRole role)
{
switch (role)
{
case MessageRole.System:
break;
case MessageRole.Human:
if (targetType == typeof(HorizontalAlignment))
{
return HorizontalAlignment.Right;
}
else if (targetType == typeof(Brush))
{
var UserMessgeBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#785dc8"));
return UserMessgeBrush;
}
else if (targetType == typeof(Visibility))
{
return Visibility.Collapsed;
}
break;
case MessageRole.Ai:
if (targetType == typeof(HorizontalAlignment))
{
return HorizontalAlignment.Left;
}
else if (targetType == typeof(Brush))
{
var ModelMessageBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#292E33"));
return ModelMessageBrush;
}
else if (targetType == typeof(Visibility))
{
return Visibility.Visible;
}
break;
case MessageRole.Chat:
break;
case MessageRole.ToolCall:
break;
case MessageRole.ToolResult:
break;
default:
break;
}
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}