diff --git a/AI/AiHelper.cs b/AI/AiHelper.cs
new file mode 100644
index 0000000..01bd126
--- /dev/null
+++ b/AI/AiHelper.cs
@@ -0,0 +1,74 @@
+using System;
+using System.ClientModel;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using OpenAI;
+
+namespace ExcelHelper.AI;
+
+class AiHelper
+{
+ ///
+ /// 火山引擎
+ ///
+ private const string API_KEY = "";
+ private const string API_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3/";
+
+ private readonly IChatClient _client;
+
+ private bool UseTool = false;
+ public AiHelper(string modelName, bool useTools = false)
+ {
+ UseTool = useTools;
+ if (useTools)
+ {
+ //TODO: add function call
+ }
+ else
+ {
+
+ var client = new OpenAIClient(new ApiKeyCredential(API_KEY), new OpenAIClientOptions
+ {
+ Endpoint = new Uri(API_ENDPOINT),
+ });
+ _client = client.AsChatClient(modelName);
+ }
+ }
+
+ public IChatClient Client => _client;
+
+ public async Task GetChatResponseAsync(string question)
+ {
+ var response = await _client.GetResponseAsync(question);
+ return response.Message.ToString();
+ }
+
+ public async IAsyncEnumerable GetStreamingResponseAsync(string question)
+ {
+ // 获取所有的工具函数
+ //var toolsList = AgentExecutor.GetDefaultTools();
+ ChatOptions chatOptions = new ChatOptions
+ {
+ // TODO: Models config
+ TopK= 10,
+ TopP = 0.85f,
+ Temperature = 0.7f,
+
+ };
+
+ if (UseTool)
+ {
+ //TODO: add function call
+ }
+ await foreach (var response in _client.GetStreamingResponseAsync(question, chatOptions))
+ {
+ yield return response.Text;
+ }
+ }
+
+}
diff --git a/AI/PromptUtil.cs b/AI/PromptUtil.cs
new file mode 100644
index 0000000..f97a4f5
--- /dev/null
+++ b/AI/PromptUtil.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ExcelHelper.AI
+{
+ class PromptUtil
+ {
+ public static string UsePrompt(string msg,string excelPrompt)
+ {
+ return $@"
+你是一个Excel公式大师,你需要帮助用户解答Excel公式相关的问题。
+
+你在回复用户的问题时,需要遵守以下规则:
+ 1. 优先保证答案的准确性,其次是简洁明了。
+ 2. 需要保证你的回答是有逻辑性的,不要让用户感到困惑。
+ 3. 可以例举2~3个例子来帮助用户更好地理解你的回答。
+ 4. 一定要保证文档格式的正确性,避免出现无法显示的内容。
+ 5. 尽量使用比较简短与易懂的公式来回答用户的问题,避免用户无法理解。
+
+现在的时间是{DateTime.Now}。
+
+当前用户提问的excel文件列信息为:{excelPrompt}
+
+用户的问题是:{msg}
+";
+ }
+ }
+}
diff --git a/App.xaml b/App.xaml
index 74a86bf..dd7bf0f 100644
--- a/App.xaml
+++ b/App.xaml
@@ -2,13 +2,18 @@
x:Class="ExcelHelper.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:ExcelHelper">
+ xmlns:local="clr-namespace:ExcelHelper"
+ xmlns:local1="clr-namespace:ExcelHelper.Converter">
+
+
+
+
diff --git a/Converter/Converters.cs b/Converter/Converters.cs
new file mode 100644
index 0000000..e2f7d2b
--- /dev/null
+++ b/Converter/Converters.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace ExcelHelper.Converter;
+
+public class BoolToColorConverter : IValueConverter
+{
+ public Brush UserMessageColor { get; set; } = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#DCF8C6"));
+ public Brush AIMessageColor { get; set; } = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#ECECEC"));
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool isUser)
+ {
+ return isUser ? UserMessageColor : AIMessageColor;
+ }
+ return AIMessageColor;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
+
+public class BoolToAlignmentConverter : IValueConverter
+{
+ public HorizontalAlignment TrueValue { get; set; } = HorizontalAlignment.Right;
+ public HorizontalAlignment FalseValue { get; set; } = HorizontalAlignment.Left;
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return boolValue ? TrueValue : FalseValue;
+ }
+ return FalseValue;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
+
+public class InverseBoolConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return !boolValue;
+ }
+ return true;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return !boolValue;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/ExcelHelper.csproj b/ExcelHelper.csproj
index 3175fbc..3ad210b 100644
--- a/ExcelHelper.csproj
+++ b/ExcelHelper.csproj
@@ -46,14 +46,18 @@
- 8.3.2
+ 8.4.0
3.5.1
+
+
+
+
1.34.2
@@ -69,4 +73,7 @@
+
+
+
\ No newline at end of file
diff --git a/Views/Components/AiMessageControll.xaml b/Views/Components/AiMessageControll.xaml
new file mode 100644
index 0000000..f3284a3
--- /dev/null
+++ b/Views/Components/AiMessageControll.xaml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Views/Components/AiMessageControll.xaml.cs b/Views/Components/AiMessageControll.xaml.cs
new file mode 100644
index 0000000..d2199d9
--- /dev/null
+++ b/Views/Components/AiMessageControll.xaml.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows.Media;
+
+namespace ExcelHelper.Views.Components
+{
+ ///
+ /// AiMessageControll.xaml 的交互逻辑
+ ///
+ public partial class AiMessageControll : UserControl
+ {
+ // 消息集合
+ public ObservableCollection Messages
+ {
+ get
+ {
+ return (ObservableCollection)GetValue(MessagesProperty);
+ }
+ set
+ {
+ SetValue(MessagesProperty, value);
+ }
+ }
+
+ public static readonly DependencyProperty MessagesProperty =
+ DependencyProperty.Register("Messages", typeof(ObservableCollection), typeof(AiMessageControll),
+ new PropertyMetadata(new ObservableCollection()));
+
+
+ // 当前输入的消息
+ public string CurrentMessage
+ {
+ get
+ {
+ return (string)GetValue(CurrentMessageProperty);
+ }
+ set
+ {
+ SetValue(CurrentMessageProperty, value);
+ }
+ }
+
+ public static readonly DependencyProperty CurrentMessageProperty =
+ DependencyProperty.Register("CurrentMessage", typeof(string), typeof(AiMessageControll), new PropertyMetadata(string.Empty));
+
+ // 发送命令
+ public ICommand SendCommand
+ {
+ get
+ {
+ return (ICommand)GetValue(SendCommandProperty);
+ }
+ set
+ {
+ SetValue(SendCommandProperty, value);
+ }
+ }
+
+ public static readonly DependencyProperty SendCommandProperty =
+ DependencyProperty.Register("SendCommand", typeof(ICommand), typeof(AiMessageControll), new PropertyMetadata(null));
+
+ // 是否正在等待AI响应
+ public bool IsWaiting
+ {
+ get
+ {
+ return (bool)GetValue(IsWaitingProperty);
+ }
+ set
+ {
+ SetValue(IsWaitingProperty, value);
+ }
+ }
+
+ public static readonly DependencyProperty IsWaitingProperty =
+ DependencyProperty.Register("IsWaiting", typeof(bool), typeof(AiMessageControll), new PropertyMetadata(false));
+
+ public AiMessageControll()
+ {
+ InitializeComponent();
+ //DataContext = this;
+ }
+
+ private void SendButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (SendCommand != null && SendCommand.CanExecute(CurrentMessage))
+ {
+ SendCommand.Execute(CurrentMessage);
+ }
+ }
+
+ private void MessageInput_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Enter && !string.IsNullOrEmpty(CurrentMessage) && !IsWaiting)
+ {
+ if (SendCommand != null && SendCommand.CanExecute(CurrentMessage))
+ {
+ SendCommand.Execute(CurrentMessage);
+ }
+ e.Handled = true;
+ }
+ }
+
+ private void Markdownview_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
+ {
+ // 将滚动事件传递给外部的 ScrollViewer
+ var scrollViewer = FindParent((DependencyObject)sender);
+ if (scrollViewer != null)
+ {
+ scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
+ e.Handled = true;
+ }
+ }
+
+ private T FindParent(DependencyObject child) where T : DependencyObject
+ {
+ DependencyObject parentObject = VisualTreeHelper.GetParent(child);
+ if (parentObject == null) return null;
+
+ if (parentObject is T parent)
+ {
+ return parent;
+ }
+ else
+ {
+ return FindParent(parentObject);
+ }
+ }
+ }
+
+ // 聊天消息类
+ public class ChatMessage : INotifyPropertyChanged
+ {
+ private string _content;
+ public string Content
+ {
+ get
+ {
+ return _content;
+ }
+ set
+ {
+ _content = value;
+ OnPropertyChanged(nameof(Content));
+ }
+ }
+ private bool _isUser;
+ public bool IsUser
+ {
+ get
+ {
+ return _isUser;
+ }
+ set
+ {
+ _isUser = value;
+ OnPropertyChanged(nameof(IsUser));
+ }
+ }
+ private DateTime _timestamp = DateTime.Now;
+ public DateTime Timestamp
+ {
+ get
+ {
+ return _timestamp;
+ }
+ set
+ {
+ _timestamp = value;
+ OnPropertyChanged(nameof(Timestamp));
+ }
+ }
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/Views/Pages/ImportExcelPage.xaml b/Views/Pages/ImportExcelPage.xaml
index e2bded5..b991c73 100644
--- a/Views/Pages/ImportExcelPage.xaml
+++ b/Views/Pages/ImportExcelPage.xaml
@@ -2,6 +2,7 @@
x:Class="ExcelHelper.Views.Pages.ImportExcelPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:components="clr-namespace:ExcelHelper.Views.Components"
xmlns:converter="clr-namespace:ExcelHelper.Converter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
@@ -52,6 +53,10 @@
Width="370"
MinWidth="370"
MaxWidth="600" />
+
+ Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}" />
-
+
+
+
+
+
diff --git a/Views/Pages/ImportExcelPage.xaml.cs b/Views/Pages/ImportExcelPage.xaml.cs
index 2946fe8..69c8bbf 100644
--- a/Views/Pages/ImportExcelPage.xaml.cs
+++ b/Views/Pages/ImportExcelPage.xaml.cs
@@ -1,4 +1,4 @@
-using System.Windows.Controls;
+using System.Windows.Controls;
using CommunityToolkit.Mvvm.Messaging;
using ExcelHelper.Message;
using ExcelHelper.Views.ViewModels;
@@ -73,4 +73,6 @@ public partial class ImportExcelPage : Page, IView, IRecipient columns)
{
ExcelColumns.Clear();
@@ -250,6 +254,267 @@ public partial class ImportViewModel : ObservableRecipient, IViewModel
};
}
+
+ [RelayCommand]
+ private async Task SendMessageToAi(string message)
+ {
+ if (!string.IsNullOrEmpty(message))
+ {
+ Messages.Add(new ChatMessage
+ {
+ Content = message,
+ IsUser = true,
+ Timestamp = DateTime.Now
+ });
+ IsAiLoading = true;
+ var aiChat = new ChatMessage {
+ Timestamp = DateTime.Now,
+ IsUser = false,
+ Content = ""
+ };
+ Messages.Add(aiChat);
+#if DEBUG
+ var results = TEST_AI_CONTENT;
+ foreach(var result in results)
+ {
+ aiChat.Content += result;
+ //await Task.Delay(1);
+ }
+#else
+ var promptedMsg = PromptUtil.UsePrompt(message,PromptString);
+ await foreach(var result in AiHelper.GetStreamingResponseAsync(promptedMsg))
+ {
+ aiChat.Content += result;
+ }
+#endif
+ IsAiLoading = false;
+ }
+ }
+
+
+ private const string TEST_AI_CONTENT = @"
+# Welcome to Leanote! 欢迎来到Leanote!
+
+## 1. 排版
+
+**粗体** *斜体*
+
+~~这是一段错误的文本。~~
+
+引用:
+
+> 引用Leanote官方的话, 为什么要做Leanote, 原因是...
+
+有充列表:
+ 1. 支持Vim
+ 2. 支持Emacs
+
+无序列表:
+
+ - 项目1
+ - 项目2
+
+
+## 2. 图片与链接
+
+图片:
+
+链接:
+
+[这是去往Leanote官方博客的链接](http://leanote.leanote.com)
+
+## 3. 标题
+
+以下是各级标题, 最多支持5级标题
+
+```
+# h1
+## h2
+### h3
+#### h4
+##### h4
+###### h5
+```
+
+## 4. 代码
+
+示例:
+
+ function get(key) {
+ return m[key];
+ }
+
+代码高亮示例:
+
+``` javascript
+/**
+* nth element in the fibonacci series.
+* @param n >= 0
+* @return the nth element, >= 0.
+*/
+function fib(n) {
+ var a = 1, b = 1;
+ var tmp;
+ while (--n >= 0) {
+ tmp = a;
+ a += b;
+ b = tmp;
+ }
+ return a;
+}
+
+document.write(fib(10));
+```
+
+```python
+class Employee:
+ empCount = 0
+
+ def __init__(self, name, salary):
+ self.name = name
+ self.salary = salary
+ Employee.empCount += 1
+```
+
+# 5. Markdown 扩展
+
+Markdown 扩展支持:
+
+* 表格
+* 定义型列表
+* Html 标签
+* 脚注
+* 目录
+* 时序图与流程图
+* MathJax 公式
+
+## 5.1 表格
+
+Item | Value
+-------- | ---
+Computer | \$1600
+Phone | \$12
+Pipe | \$1
+
+可以指定对齐方式, 如Item列左对齐, Value列右对齐, Qty列居中对齐
+
+| Item | Value | Qty |
+| :------- | ----: | :---: |
+| Computer | \$1600 | 5 |
+| Phone | \$12 | 12 |
+| Pipe | \$1 | 234 |
+
+
+## 5.2 定义型列表
+
+名词 1
+: 定义 1(左侧有一个可见的冒号和四个不可见的空格)
+
+代码块 2
+: 这是代码块的定义(左侧有一个可见的冒号和四个不可见的空格)
+
+ 代码块(左侧有八个不可见的空格)
+
+## 5.3 Html 标签
+
+支持在 Markdown 语法中嵌套 Html 标签,譬如,你可以用 Html 写一个纵跨两行的表格:
+
+
+
+ 值班人员 |
+ 星期一 |
+ 星期二 |
+ 星期三 |
+
+
+ 李强 |
+ 张明 |
+ 王平 |
+
+
+
+
+
+
+ 值班人员 |
+ 星期一 |
+ 星期二 |
+ 星期三 |
+
+
+ 李强 |
+ 张明 |
+ 王平 |
+
+
+
+**提示**, 如果想对图片的宽度和高度进行控制, 你也可以通过img标签, 如:
+
+
+
+## 5.4 脚注
+
+Leanote[^footnote]来创建一个脚注
+ [^footnote]: Leanote是一款强大的开源云笔记产品.
+
+## 5.5 目录
+
+通过 `[TOC]` 在文档中插入目录, 如:
+
+[TOC]
+
+## 5.6 时序图与流程图
+
+```sequence
+Alice->Bob: Hello Bob, how are you?
+Note right of Bob: Bob thinks
+Bob-->Alice: I am good thanks!
+```
+
+流程图:
+
+```flow
+st=>start: Start
+e=>end
+op=>operation: My Operation
+cond=>condition: Yes or No?
+
+st->op->cond
+cond(yes)->e
+cond(no)->op
+```
+
+> **提示:** 更多关于时序图与流程图的语法请参考:
+
+> - [时序图语法](http://bramp.github.io/js-sequence-diagrams/)
+> - [流程图语法](http://adrai.github.io/flowchart.js)
+
+## 5.7 MathJax 公式
+
+$ 表示行内公式:
+
+质能守恒方程可以用一个很简洁的方程式 $E=mc^2$ 来表达。
+
+$$ 表示整行公式:
+
+$$\sum_{i=1}^n a_i=0$$
+
+$$f(x_1,x_x,\ldots,x_n) = x_1^2 + x_2^2 + \cdots + x_n^2 $$
+
+$$\sum^{j-1}_{k=0}{\widehat{\gamma}_{kj} z_k}$$
+
+更复杂的公式:
+$$
+\begin{eqnarray}
+\vec\nabla \times (\vec\nabla f) & = & 0 \cdots\cdots梯度场必是无旋场\\
+\vec\nabla \cdot(\vec\nabla \times \vec F) & = & 0\cdots\cdots旋度场必是无散场\\
+\vec\nabla \cdot (\vec\nabla f) & = & {\vec\nabla}^2f\\
+\vec\nabla \times(\vec\nabla \times \vec F) & = & \vec\nabla(\vec\nabla \cdot \vec F) - {\vec\nabla}^2 \vec F\\
+\end{eqnarray}
+$$
+
+访问 [MathJax](http://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference) 参考更多使用方法。
+";
#region Props
[ObservableProperty]
private IEnumerable _excelData;
@@ -263,12 +528,20 @@ public partial class ImportViewModel : ObservableRecipient, IViewModel
[ObservableProperty]
private ObservableCollection _tableColumns = [];
+ [ObservableProperty]
+ private ObservableCollection _messages = [];
+
+
+ [ObservableProperty]
+ private string _currentMessage;
///
/// 是否正在加载
///
[ObservableProperty]
private bool _isLoading = false;
+ [ObservableProperty]
+ private bool _isAiLoading = false;
///
/// 使用首行作为表头
///