diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj
index 0f3f990..d764cc6 100644
--- a/LinkToolAddin.csproj
+++ b/LinkToolAddin.csproj
@@ -120,6 +120,18 @@
Always
+
+
+ Always
+
+
+
+ Always
+
+
+
+ Always
+
diff --git a/common/LocalResource.cs b/common/LocalResource.cs
index fc1e719..4f4e53a 100644
--- a/common/LocalResource.cs
+++ b/common/LocalResource.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
namespace LinkToolAddin.common;
@@ -8,12 +10,6 @@ public class LocalResource
public static string ReadFileByResource(string resourceName)
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
-
- string[] resourceNames = assembly.GetManifestResourceNames();
- foreach (string name in resourceNames)
- {
- Console.WriteLine(name);
- }
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
@@ -28,4 +24,24 @@ public class LocalResource
}
}
}
+
+ public static BitmapImage ReadImageByResource(string resourceName)
+ {
+ var assembly = System.Reflection.Assembly.GetExecutingAssembly();
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
+ {
+ if (stream == null)
+ {
+ Console.WriteLine("资源未找到,请检查资源名称是否正确。");
+ return null;
+ }
+
+ // 如果是 WPF,可以转换为 BitmapImage
+ BitmapImage bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.StreamSource = stream;
+ bitmap.EndInit();
+ return bitmap;
+ }
+ }
}
\ No newline at end of file
diff --git a/host/Gateway.cs b/host/Gateway.cs
index 2a789c5..63ef2b2 100644
--- a/host/Gateway.cs
+++ b/host/Gateway.cs
@@ -328,14 +328,17 @@ public class Gateway
else
{
//包含Prompt调用请求的消息
- MessageListItem chatMessageListItem = new ChatMessageItem()
+ if (remainingPrompt != "")
{
- content = remainingPrompt,
- role = "assistant",
- type = MessageType.CHAT_MESSAGE,
- id = timestamp.ToString()
- };
- callback?.Invoke(chatMessageListItem);
+ MessageListItem chatMessageListItem = new ChatMessageItem()
+ {
+ content = remainingPrompt,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE,
+ id = timestamp.ToString()
+ };
+ callback?.Invoke(chatMessageListItem);
+ }
XElement promptUse = XElement.Parse(matchedPrompt);
string promptKey = promptUse.Element("name")?.Value;
string promptArgs = promptUse.Element("arguments")?.Value;
@@ -352,17 +355,20 @@ public class Gateway
else
{
//包含工具调用请求的消息
- MessageListItem chatMessageListItem = new ChatMessageItem()
+ if (remaining != "")
{
- content = remaining,
- role = "assistant",
- type = MessageType.CHAT_MESSAGE,
- id = timestamp.ToString()
- };
- Application.Current.Dispatcher.Invoke(() =>
- {
- callback?.Invoke(chatMessageListItem);
- });
+ MessageListItem chatMessageListItem = new ChatMessageItem()
+ {
+ content = remaining,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE,
+ id = timestamp.ToString()
+ };
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ callback?.Invoke(chatMessageListItem);
+ });
+ }
XElement toolUse = XElement.Parse(matched);
string fullToolName = toolUse.Element("name")?.Value;
string toolArgs = toolUse.Element("arguments")?.Value;
@@ -415,7 +421,8 @@ public class Gateway
type = MessageType.TOOL_MESSAGE,
status = toolResponse.IsError ? "fail" : "success",
content = JsonConvert.SerializeObject(toolResponse),
- id = (timestamp + 1).ToString()
+ id = (timestamp + 1).ToString(),
+ role = "user"
};
messages.Add(new Message
{
@@ -441,7 +448,8 @@ public class Gateway
type = MessageType.TOOL_MESSAGE,
status = toolResponse.IsError ? "fail" : "success",
content = JsonConvert.SerializeObject(toolResponse),
- id = (timestamp + 1).ToString()
+ id = (timestamp + 1).ToString(),
+ role = "user"
};
messages.Add(new Message
{
@@ -486,7 +494,8 @@ public class Gateway
type = MessageType.TOOL_MESSAGE,
status = "fail",
content = JsonConvert.SerializeObject(innerResult),
- id = (timestamp + 1).ToString()
+ id = (timestamp + 1).ToString(),
+ role = "user"
};
messages.Add(new Message
{
@@ -507,7 +516,8 @@ public class Gateway
type = MessageType.TOOL_MESSAGE,
status = "success",
content = JsonConvert.SerializeObject(innerResult),
- id = (timestamp + 1).ToString()
+ id = (timestamp + 1).ToString(),
+ role = "user"
};
messages.Add(new Message
{
diff --git a/host/McpServerList.cs b/host/McpServerList.cs
index ca7415d..b452abc 100644
--- a/host/McpServerList.cs
+++ b/host/McpServerList.cs
@@ -35,53 +35,53 @@ public class McpServerList
Description = "可以调用进行查询知识库,获取相关参考信息。",
IsActive = true
});
- servers.Add("filesystem", new StdioMcpServer()
- {
- Name = "filesystem",
- Type = "stdio",
- Command = "npx",
- Args = new List()
- {
- "-y",
- "@modelcontextprotocol/server-filesystem",
- "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData"
- }
- });
- servers.Add("fetch", new StdioMcpServer()
- {
- Name = "fetch",
- Type = "stdio",
- Command = "uvx",
- Args = new List()
- {
- "mcp-server-fetch"
- }
- });
- servers.Add("bing-search", new StdioMcpServer()
- {
- Name = "bing-search",
- Type = "stdio",
- Command = "npx",
- Args = new List()
- {
- "bing-cn-mcp"
- }
- });
- servers.Add("mcp-python-interpreter", new StdioMcpServer()
- {
- Name = "mcp-python-interpreter",
- Type = "stdio",
- Command = "uvx",
- Args = new List()
- {
- "--native-tls",
- "mcp-python-interpreter",
- "--dir",
- "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData",
- "--python-path",
- "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe"
- }
- });
+ // servers.Add("filesystem", new StdioMcpServer()
+ // {
+ // Name = "filesystem",
+ // Type = "stdio",
+ // Command = "npx",
+ // Args = new List()
+ // {
+ // "-y",
+ // "@modelcontextprotocol/server-filesystem",
+ // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData"
+ // }
+ // });
+ // servers.Add("fetch", new StdioMcpServer()
+ // {
+ // Name = "fetch",
+ // Type = "stdio",
+ // Command = "uvx",
+ // Args = new List()
+ // {
+ // "mcp-server-fetch"
+ // }
+ // });
+ // servers.Add("bing-search", new StdioMcpServer()
+ // {
+ // Name = "bing-search",
+ // Type = "stdio",
+ // Command = "npx",
+ // Args = new List()
+ // {
+ // "bing-cn-mcp"
+ // }
+ // });
+ // servers.Add("mcp-python-interpreter", new StdioMcpServer()
+ // {
+ // Name = "mcp-python-interpreter",
+ // Type = "stdio",
+ // Command = "uvx",
+ // Args = new List()
+ // {
+ // "--native-tls",
+ // "mcp-python-interpreter",
+ // "--dir",
+ // "D:\\01_Project\\20250305_LinkTool\\20250420_AiDemoProject\\TestData",
+ // "--python-path",
+ // "C:\\Program Files\\ArcGIS\\Pro\\bin\\Python\\envs\\custom\\python.exe"
+ // }
+ // });
}
public McpServer GetServer(string name)
diff --git a/resource/img/linktool.png b/resource/img/linktool.png
new file mode 100644
index 0000000..37264c2
Binary files /dev/null and b/resource/img/linktool.png differ
diff --git a/resource/img/tool.png b/resource/img/tool.png
new file mode 100644
index 0000000..09bee43
Binary files /dev/null and b/resource/img/tool.png differ
diff --git a/resource/img/user.png b/resource/img/user.png
new file mode 100644
index 0000000..a992685
Binary files /dev/null and b/resource/img/user.png differ
diff --git a/ui/dockpane/DialogDockpane.xaml b/ui/dockpane/DialogDockpane.xaml
index 84e7de1..fafe132 100644
--- a/ui/dockpane/DialogDockpane.xaml
+++ b/ui/dockpane/DialogDockpane.xaml
@@ -5,9 +5,10 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:LinkToolAddin.ui.dockpane"
xmlns:extensions="clr-namespace:ArcGIS.Desktop.Extensions;assembly=ArcGIS.Desktop.Extensions"
- xmlns:controls="clr-namespace:ArcGIS.Desktop.Framework.Controls;assembly=ArcGIS.Desktop.Framework"
+ xmlns:controls="clr-namespace:ArcGIS.Desktop.Framework.Controls;assembly=ArcGIS.Desktop.Framework"
+ xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d"
- d:DesignHeight="300" d:DesignWidth="300"
+ d:DesignHeight="650" d:DesignWidth="300"
d:DataContext="{Binding Path=ui.DialogDockpaneViewModel}">
@@ -16,13 +17,27 @@
-
+
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs
index aa2ea7f..1148b88 100644
--- a/ui/dockpane/DialogDockpane.xaml.cs
+++ b/ui/dockpane/DialogDockpane.xaml.cs
@@ -1,5 +1,7 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Net.Http;
@@ -12,10 +14,15 @@ 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;
@@ -29,17 +36,26 @@ 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)
@@ -81,5 +97,190 @@ namespace LinkToolAddin.ui.dockpane
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,"qwen-max",defaultGdbPath,NewMessage_Recall);
+ }
+
+ public void NewMessage_Recall(MessageListItem msg)
+ {
+ string msgId = msg.id;
+ log.Info(msg.content);
+ 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
+ {
+ Border border = GetAiChatBorder(msg);
+ borderItemsDict[msgId] = border;
+ ChatHistoryStackPanel.Children.Add(border);
+ }
+ }
+ else
+ {
+ //已有该消息,只需要修改内容
+ messageDict[msgId] = msg;
+ 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;
+ }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;
+ }
+ }
+ else
+ {
+ Border borderItem = borderItemsDict[msgId];
+ Grid grid = borderItem.Child as Grid;
+ TextBox textBox = grid.Children[1] as TextBox;
+ textBox.Text = msg.content;
+ }
+ }
+ }
+
+ 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.Background = Brushes.Transparent;
+ textBox.Text = msg.content;
+ textBox.TextWrapping = TextWrapping.Wrap;
+ border.Child = grid;
+ return border;
+ }
+
+ 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();
+ grid.Children.Add(icon);
+ grid.Children.Add(textBox);
+ Grid.SetColumn(icon, 1);
+ Grid.SetColumn(textBox, 0);
+ textBox.Background = Brushes.Transparent;
+ 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.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)});
+ TextBlock textBlock = new TextBlock();
+ textBlock.Text = (msg as ToolMessageItem).toolName;
+ textBlock.TextWrapping = TextWrapping.Wrap;
+ grid.Children.Add(icon);
+ grid.Children.Add(textBlock);
+ Grid.SetColumn(icon, 0);
+ Grid.SetColumn(textBlock, 1);
+ border.Child = grid;
+ return border;
+ }
+
+ 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);
+ }
}
}
diff --git a/ui/dockpane/DialogDockpaneViewModel.cs b/ui/dockpane/DialogDockpaneViewModel.cs
index 1d7a4e5..42258fc 100644
--- a/ui/dockpane/DialogDockpaneViewModel.cs
+++ b/ui/dockpane/DialogDockpaneViewModel.cs
@@ -14,6 +14,7 @@ using ArcGIS.Desktop.Layouts;
using ArcGIS.Desktop.Mapping;
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -27,8 +28,14 @@ namespace LinkToolAddin.ui.dockpane
internal class DialogDockpaneViewModel : DockPane
{
private const string _dockPaneID = "DialogDockpane";
+
+ public ObservableCollection Items { get; set; }
- protected DialogDockpaneViewModel() { }
+ protected DialogDockpaneViewModel()
+ {
+ Items = new ObservableCollection();
+ Items.Add(new ItemModel(){Content = "adfdfdafdfs"});
+ }
///
/// Show the DockPane.