From 52148f693611bf4c222946d36cd06488dfcffa2d Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Tue, 3 Jun 2025 18:35:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E7=9A=84=E5=B7=A5=E5=85=B7=E3=80=81=E6=B6=88=E6=81=AFUI?= =?UTF-8?q?=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 12 ++ common/LocalResource.cs | 28 +++- host/Gateway.cs | 52 ++++--- host/McpServerList.cs | 94 ++++++------ resource/img/linktool.png | Bin 0 -> 931 bytes resource/img/tool.png | Bin 0 -> 1082 bytes resource/img/user.png | Bin 0 -> 784 bytes ui/dockpane/DialogDockpane.xaml | 29 +++- ui/dockpane/DialogDockpane.xaml.cs | 203 ++++++++++++++++++++++++- ui/dockpane/DialogDockpaneViewModel.cs | 9 +- 10 files changed, 344 insertions(+), 83 deletions(-) create mode 100644 resource/img/linktool.png create mode 100644 resource/img/tool.png create mode 100644 resource/img/user.png 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 0000000000000000000000000000000000000000..37264c2046f6c4a22ba32068324771e29f4246ed GIT binary patch literal 931 zcmV;U16=%xP)Px&T}ebiR9Hu?SM6=vKnx|743N45Q~~M$nIg_4iGSK1!p;zlf7+hJ&J+dW08t01 zn*m(0;8B!l>P}Vw=U;rf<0JVVKM>CRH(2auL@+|2JUorK+E0d0DKq#P`&Wx!IlyI} z0o=(w%mJuS17DtZ8w41&-=w0%fA=4g+rppq0?>FBdmLat9U^}N1arX!^AZm0i}GSb zh)e#70JeXp!~0JGu#fTsh*$v8BYRDN1$&D`<3B%cR)go)caDfRG+4YXG$#1pRbSX9edA@51w}<-chli-dwAv#{l5|V=~;@ zD6{~u@r*7uf*8kq!5il@3qbB%zSj>|ppVkhCj!kw$6heG_;H&z=d6`|4NlvT=luqj2&WvobWaY2G7e%J$uaX`P(Gp!l+u1J#}wN{{pEG7{yE}j?O zNbFvS>f&wbi7R}Qf~F2*isc&0SY%Qp^ig7CzVbEiimWje=C0KA0iYGtmSqJfPQ#W< zJT^7_G|$iaMb%S61OT(GR{&-BEwopJIhORhtpYi>DzM+gRHEmEFk_p{kscgqcQ8w) zE)o(^9-9F|)T`no0Dy9zD^Hdf!I;fm0Qinqf~r8(RZlW;8FG5!Go3z^1Q1f&zU#@2 zU7n^S+A9T#BF6Q>rsj;jpua|JajUvvGj3TmbSa^e<&i#7_W^*BOt9 zK6dqjSaI{@)3<5>br-ATaa{9Z=2emckor9~ZPo3#-Nj@Q7P3_XXB1d773&*X*sT0g zQf~o}E>W*B=IpI5F)S}p4sSx3Yq{+b=*iuXJ>}6XzH}b7pF);Z z*#ij=Px&^hrcPR9Hu?m%VNjNf5`YdY6mc z_&L+R*Y9-98WCLNVPnk8_irA}Qggrc8^#-)9o>IHL_Z`pUJ$_Uhxv5-Bt}<wFAw?&@+LW-f(ijF!H|x=#3_S7lr*r zpDG2xbL(8GVS2*}!{7Gbb_;8_Wp4*&%pOA&)(&uWu=y(@{)UL}^P;$N9E3VqKjj7Q z@;xA$VW+bli`N6N7n~O{qFsuw_JkM|FQ75w*g(^m_8=f^Wc$Fgwm@(L5OcD2lW!4&>h(&zcsRPjBNVzYS zZ~{a*c!8Pw@<`rnBHY?ruQldXLO(M`x>Y1+!j~aW2tM~ zlrbx**n46Njd{+ek+c~$K%8a!kH>n$ifD=ckZ>HAs)God==d~Tk_q8I1MnvhbOGU? zY=8P=4TK9vHlLnv+ETvUS1pL|QDJrQxH+W$}Zod>`z8!5KS^-F0iQr#E zI2Y#yG1T*#0BjF&EI=%(l?!*TnQrF=0Gv*Hq7-IZh(>_exUTW6d;&W6tq`>UO~$#B z6@{I3P3JX392K9X+tD^0Nr1ShRJt2qfYh8)0u2xd@RDf+CvI7+RBfEgGLMJW zDq)<6?x4XZDX) z%jR_u(#*=+B4|rGqPk)vjf%8RI_9ZPi1*PDGAHcgJ8TzHV-C1FW(QBk&I7W!T&duy zM9k}qizfo8U#kEhK&cK(18^%1B0T<^t6Q6sYay%B?iw7oz9_b&7f6PHHwdRDniWJF zCovvUvTsb|Bo)62*hb|RXb3R)+`o>3>)I3-r78FL;7*C}KXJ!YzN#wwE#(0Z;Tq9+ za$5iev87%}{JD|?l8Hz}$-lqHlbqcydP*JOD~scR7Kw-0EdT%j07*qoM6N<$f@+}i A%m4rY literal 0 HcmV?d00001 diff --git a/resource/img/user.png b/resource/img/user.png new file mode 100644 index 0000000000000000000000000000000000000000..a992685890033c8da9d9ba147f96b6195e87355e GIT binary patch literal 784 zcmV+r1MmEaP)Px%%1J~)R9HvFS3z>yFbo9bsXP5a>K^Lc?0%y5OA^1t&P$xWBMwy<_Dds zW1$0zkU$!wq?2_?W=xUG#qI(^xbcHGJ|9K{xcjr17X>^x;8@JvU#d$)c)cK$Z|_cr zF_#()VD@*h1;j@HXbw$3B@jKI-=Cg_0OF;Tb&^8JRRkE zdc3zvVgd*bztH;a!>Jrn&v=akGQpRI4SYUNe{AA4R{&Ja%Q|zoTjy(!P=Eh5Jvm(@ zK>T!?0ougtt^mxcB`==WNNbDsyMK#SK~9vh82WP|H^7Rmb4)_@o;lV-AjlX1#hdGz zw&gJZ3OlLyHG8_eEdZ&LB@_WMtCqz7pQlIDJ^uHk<-}e3jCLcbe-40bUX_Ws0HWN_}L&1v1~i`o6mFMlmZ~^v8Y9y7K?VIiY5K-S~O_~hQCCeGno-4fGgYe z%ykwh?TB1JC1*lwt{XCLdm(p2X^(z-$8zI)z(p_umOP5Nv5aFFan%J;H?Z`5AM^s? zt%MEZ5R3gm-vjdEAOQN5cJ^;SMw(AEUQ^fbZR`X+8o*E&3|)6~0RI8<6D&WHlhY;u O0000 @@ -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.