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; } } /// /// Interaction logic for DialogDockpaneView.xaml /// public partial class DialogDockpaneView : UserControl { private static ILog log = LogManager.GetLogger(typeof(DialogDockpaneView)); private List idList = new List(); private ConcurrentDictionary messageDict = new ConcurrentDictionary(); private ConcurrentDictionary borderItemsDict = new ConcurrentDictionary(); 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); }else if (msg.type == MessageType.CHAT_MESSAGE) { Border border = GetUserChatBorder(msg); borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); } } else if(msg.role == "assistant") { if (msg.type == MessageType.REASON_MESSAGE) { Border border = GetAiReasonBorder(msg); borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); }else if (msg.type == MessageType.CHAT_MESSAGE) { Border border = GetAiChatBorder(msg); borderItemsDict[msgId] = border; ChatHistoryStackPanel.Children.Add(border); } } } 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(), 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(); } 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(); } } }