LinkToolAddin/ui/dockpane/DialogDockpane.xaml.cs

493 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using System.Threading;
using System.Windows.Documents;
using System.Windows.Media;
using ArcGIS.Desktop.Core;
using ArcGIS.Desktop.Core.Geoprocessing;
using LinkToolAddin.client;
using LinkToolAddin.common;
using LinkToolAddin.host;
using LinkToolAddin.host.llm;
using LinkToolAddin.host.llm.entity;
using LinkToolAddin.message;
using LinkToolAddin.resource;
using LinkToolAddin.server;
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Layout;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
using Newtonsoft.Json;
namespace LinkToolAddin.ui.dockpane
{
public class ItemModel
{
public string Content { get; set; }
}
/// <summary>
/// Interaction logic for DialogDockpaneView.xaml
/// </summary>
public partial class DialogDockpaneView : UserControl
{
private static ILog log = LogManager.GetLogger(typeof(DialogDockpaneView));
private List<string> idList = new List<string>();
private ConcurrentDictionary<string,MessageListItem> messageDict = new ConcurrentDictionary<string,MessageListItem>();
private ConcurrentDictionary<string,Border> borderItemsDict = new ConcurrentDictionary<string,Border>();
public DialogDockpaneView()
{
InitLogger();
InitializeComponent();
DataContext = this;
}
private async void TestServer_OnClick(object sender, RoutedEventArgs e)
{
log.Info("TestServer Clicked");
}
protected void InitLogger()
{
// 1. 创建控制台输出器Appender
var consoleAppender = new ConsoleAppender
{
Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"),
Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别
};
consoleAppender.ActivateOptions(); // 激活配置
// 2. 创建文件滚动输出器(按大小滚动)
var fileAppender = new RollingFileAppender
{
File = Path.Combine("Logs", "D:\\linktool_app.log"), // 日志文件路径
AppendToFile = true, // 追加模式
RollingStyle = RollingFileAppender.RollingMode.Size, // 按文件大小滚动
MaxSizeRollBackups = 10, // 保留 10 个历史文件
MaximumFileSize = "1MB", // 单个文件最大 1MB
StaticLogFileName = true, // 固定文件名(否则自动追加序号)
Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"),
Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别
};
fileAppender.ActivateOptions(); // 激活配置
// 3. 直接通过 BasicConfigurator 注册 Appender
BasicConfigurator.Configure(consoleAppender, fileAppender);
log = LogManager.GetLogger(typeof(DialogDockpaneView));
// 测试日志输出
log.Debug("Debug 日志(控制台可见)");
log.Info("Info 日志(控制台和文件可见)");
log.Error("Error 日志(严重问题)");
}
private void SendButton_OnClick(object sender, RoutedEventArgs e)
{
string question = QuestionTextbox.Text;
string defaultGdbPath = Project.Current.DefaultGeodatabasePath;
string gdbPath = @"";
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
MessageListItem userMsg = new ChatMessageItem()
{
content = question,
role = "user",
type = MessageType.CHAT_MESSAGE,
id = timestamp.ToString()
};
Border userMsgBoder = GetUserChatBorder(userMsg);
idList.Add(timestamp.ToString());
messageDict[timestamp.ToString()] = userMsg;
QuestionTextbox.Text = "";
borderItemsDict[timestamp.ToString()] = userMsgBoder;
ChatHistoryStackPanel.Children.Add(userMsgBoder);
Gateway.SendMessageStream(question,"qwen3-235b-a22b",defaultGdbPath,NewMessage_Recall);
}
public void NewMessage_Recall(MessageListItem msg)
{
string msgId = msg.id;
log.Info(msg.content);
double verticalOffset = ScrollViewer.VerticalOffset;
double viewportHeight = ScrollViewer.ViewportHeight;
double contentHeight = ChatHistoryStackPanel.ActualHeight;
double tolerance = 24;
if (!idList.Contains(msgId))
{
//不存在该消息需添加到ListView中
if (msg.content == "")
{
return;
}
idList.Add(msgId);
messageDict[msgId] = msg;
if (msg.role == "user")
{
if (msg.type == MessageType.TOOL_MESSAGE)
{
Border border = GetToolChatBorder(msg);
borderItemsDict[msgId] = border;
ChatHistoryStackPanel.Children.Add(border);
StatusTextBlock.Text = "正在执行工具";
}else if (msg.type == MessageType.CHAT_MESSAGE)
{
Border border = GetUserChatBorder(msg);
borderItemsDict[msgId] = border;
ChatHistoryStackPanel.Children.Add(border);
StatusTextBlock.Text = "正在读取用户输入";
}
}
else if(msg.role == "assistant")
{
if (msg.type == MessageType.REASON_MESSAGE)
{
Border border = GetAiReasonBorder(msg);
borderItemsDict[msgId] = border;
ChatHistoryStackPanel.Children.Add(border);
StatusTextBlock.Text = "深度思考中";
}else if (msg.type == MessageType.CHAT_MESSAGE)
{
Border border = GetAiChatBorder(msg);
borderItemsDict[msgId] = border;
ChatHistoryStackPanel.Children.Add(border);
StatusTextBlock.Text = "回答生成中";
}else if (msg.type == MessageType.END_TAG)
{
StatusTextBlock.Text = "";
}
}
}
else
{
//已有该消息,只需要修改内容
messageDict[msgId] = msg;
if (msg.content == "")
{
ChatHistoryStackPanel.Children.Remove(borderItemsDict[msgId]);
borderItemsDict.TryRemove(msgId, out Border border);
messageDict.TryRemove(msgId, out MessageListItem tempMsg);
idList.Remove(msgId);
}
if (msg.role == "user")
{
if (msg.type == MessageType.TOOL_MESSAGE)
{
Border borderItem = borderItemsDict[msgId];
Grid grid = borderItem.Child as Grid;
TextBlock textBlock = grid.Children[1] as TextBlock;
textBlock.Text = (msg as ToolMessageItem).toolName;
StatusTextBlock.Text = "正在执行工具";
}else if (msg.type == MessageType.CHAT_MESSAGE)
{
Border borderItem = borderItemsDict[msgId];
Grid grid = borderItem.Child as Grid;
TextBox textBox = grid.Children[1] as TextBox;
textBox.Text = msg.content;
StatusTextBlock.Text = "正在读取用户输入";
}
}
else
{
if (msg.type == MessageType.REASON_MESSAGE)
{
Border borderItem = borderItemsDict[msgId];
Grid grid = borderItem.Child as Grid;
TextBox textBox = grid.Children[0] as TextBox;
textBox.Text = msg.content;
StatusTextBlock.Text = "深度思考中";
}else if (msg.type == MessageType.CHAT_MESSAGE)
{
Border borderItem = borderItemsDict[msgId];
Grid grid = borderItem.Child as Grid;
TextBox textBox = grid.Children[1] as TextBox;
textBox.Text = msg.content;
StatusTextBlock.Text = "回答生成中";
}else if (msg.type == MessageType.END_TAG)
{
StatusTextBlock.Text = "";
}
}
}
if (Math.Abs(verticalOffset + viewportHeight - contentHeight) < tolerance)
{
ScrollViewer.ScrollToBottom();
}
}
private Border GetAiChatBorder(MessageListItem msg)
{
Border border = new Border();
border.Margin = new Thickness(8, 12, 8, 12);
border.BorderThickness = new Thickness(0);
// border.Background = Brushes.DarkSeaGreen;
Grid grid = new Grid();
Image icon = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.linktool.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top
};
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)});
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)});
TextBox textBox = new TextBox();
grid.Children.Add(icon);
grid.Children.Add(textBox);
Grid.SetColumn(icon, 0);
Grid.SetColumn(textBox, 1);
textBox.IsReadOnly = true;
textBox.BorderThickness = new Thickness(0);
textBox.Background = Brushes.Transparent;
textBox.Text = msg.content;
textBox.TextWrapping = TextWrapping.Wrap;
border.Child = grid;
return border;
}
private Border GetAiReasonBorder(MessageListItem msg)
{
Border border = new Border();
border.Margin = new Thickness(8, 12, 8, 12);
border.BorderThickness = new Thickness(0);
// border.Background = Brushes.DarkSeaGreen;
Grid grid = new Grid();
Image icon = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.linktool.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top
};
Image fold = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.fold.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top
};
Image unfold = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.unfold.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top
};
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)});
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)});
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(16, GridUnitType.Pixel)});
TextBox textBox = new TextBox();
// grid.Children.Add(icon);
grid.Children.Add(textBox);
// Grid.SetColumn(icon, 0);
Grid.SetColumn(textBox, 1);
textBox.IsReadOnly = true;
textBox.Foreground = Brushes.Gray;
textBox.BorderThickness = new Thickness(2,0,0,0);
textBox.BorderBrush = Brushes.Gray;
textBox.Background = Brushes.Transparent;
textBox.Text = msg.content;
textBox.TextWrapping = TextWrapping.Wrap;
Button button = new Button();
StackPanel panel = new StackPanel();
panel.Orientation = Orientation.Horizontal;
panel.Children.Add(fold);
button.Content = panel;
button.BorderThickness = new Thickness(0);
button.Background = Brushes.Transparent;
button.Width = 16;
button.Height = 16;
button.Padding = new Thickness(0);
button.HorizontalAlignment = HorizontalAlignment.Center;
button.VerticalAlignment = VerticalAlignment.Top;
button.Tag = "fold";
button.Click += FoldButton_OnClick;
Grid.SetColumn(button, 2);
grid.Children.Add(button);
border.Child = grid;
return border;
}
private void FoldButton_OnClick(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
string tag = button.Tag.ToString();
if (tag == "fold")
{
button.Tag = "unfold";
Grid grid = button.Parent as Grid;
TextBox textBox = grid.Children[0] as TextBox;
textBox.Visibility = Visibility.Collapsed;
StackPanel stackPanel = button.Content as StackPanel;
Image unfold = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.unfold.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top
};
stackPanel.Children.Clear();
stackPanel.Children.Add(unfold);
}
else
{
button.Tag = "fold";
Image fold = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.fold.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top
};
Grid grid = button.Parent as Grid;
TextBox textBox = grid.Children[0] as TextBox;
textBox.Visibility = Visibility.Visible;
StackPanel stackPanel = button.Content as StackPanel;
stackPanel.Children.Clear();
stackPanel.Children.Add(fold);
}
}
private Border GetUserChatBorder(MessageListItem msg)
{
Border border = new Border();
border.Margin = new Thickness(8, 12, 8, 12);
border.BorderThickness = new Thickness(0);
// border.Background = Brushes.DarkSeaGreen;
Grid grid = new Grid();
Image icon = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.user.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top
};
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)});
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)});
TextBox textBox = new TextBox();
textBox.HorizontalAlignment = HorizontalAlignment.Right;
grid.Children.Add(icon);
grid.Children.Add(textBox);
Grid.SetColumn(icon, 1);
Grid.SetColumn(textBox, 0);
textBox.Background = Brushes.Transparent;
textBox.IsReadOnly = true;
textBox.BorderThickness = new Thickness(0);
textBox.Text = msg.content;
textBox.TextWrapping = TextWrapping.Wrap;
border.Child = grid;
return border;
}
private Border GetToolChatBorder(MessageListItem msg)
{
Border border = new Border();
border.Margin = new Thickness(24);
border.Padding = new Thickness(8);
border.BorderThickness = new Thickness(1);
border.BorderBrush = Brushes.Gray;
border.CornerRadius = new CornerRadius(3);
// border.Background = Brushes.DarkSeaGreen;
Grid grid = new Grid();
Image icon = new Image()
{
Source = LocalResource.ReadImageByResource("LinkToolAddin.resource.img.tool.png"),
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center
};
icon.Margin = new Thickness(0, 0, 8, 0);
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(24, GridUnitType.Pixel)});
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1, GridUnitType.Star)});
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(36, GridUnitType.Pixel)});
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(36, GridUnitType.Pixel)});
TextBlock textBlock = new TextBlock();
ToolMessageItem toolMsg = msg as ToolMessageItem;
textBlock.Text = toolMsg.toolName + " | " + toolMsg.status;
textBlock.TextWrapping = TextWrapping.Wrap;
grid.Children.Add(icon);
grid.Children.Add(textBlock);
Grid.SetColumn(icon, 0);
Grid.SetColumn(textBlock, 1);
Button argsButton = new Button();
argsButton.Content = "参数";
argsButton.Tag = msg as ToolMessageItem;
argsButton.Click += ToolArgsButton_OnClick;
border.Child = grid;
Button resButton = new Button();
resButton.Content = "结果";
resButton.Tag = msg as ToolMessageItem;
resButton.Click += ToolResButton_OnClick;
grid.Children.Add(argsButton);
Grid.SetColumn(argsButton, 2);
grid.Children.Add(resButton);
Grid.SetColumn(resButton, 3);
return border;
}
private void ToolArgsButton_OnClick(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
ToolMessageItem toolItem = button.Tag as ToolMessageItem;
ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(JsonConvert.SerializeObject(toolItem.toolParams),toolItem.toolName+"工具参数");
}
private void ToolResButton_OnClick(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
ToolMessageItem toolItem = button.Tag as ToolMessageItem;
ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(toolItem.result,toolItem.toolName+"运行结果");
}
private void TestButton_OnClick(object sender, RoutedEventArgs e)
{
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
MessageListItem toolMessageItem = new ToolMessageItem
{
toolName = "toolName",
toolParams = new Dictionary<string,object>(),
type = MessageType.TOOL_MESSAGE,
status = "success",
content = "JsonConvert.SerializeObject(toolResponse)",
id = (timestamp + 1).ToString(),
role = "user"
};
NewMessage_Recall(toolMessageItem);
}
private void ClearButton_OnClick(object sender, RoutedEventArgs e)
{
idList.Clear();
messageDict.Clear();
borderItemsDict.Clear();
ChatHistoryStackPanel.Children.Clear();
QuestionTextbox.Clear();
StatusTextBlock.Text = "";
}
private void TopButton_OnClick(object sender, RoutedEventArgs e)
{
ScrollViewer.ScrollToTop();
}
private void BottomButton_OnClick(object sender, RoutedEventArgs e)
{
ScrollViewer.ScrollToBottom();
}
private void StopButton_OnClick(object sender, RoutedEventArgs e)
{
Gateway.StopConversation();
StatusTextBlock.Text = "";
}
}
}