diff --git a/Config.daml b/Config.daml
index 9147f6f..18a04f2 100644
--- a/Config.daml
+++ b/Config.daml
@@ -23,9 +23,10 @@
-
-
-
+
+
+
+
@@ -40,11 +41,17 @@
+
+
+
+
diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj
index f683eae..e0d0d0e 100644
--- a/LinkToolAddin.csproj
+++ b/LinkToolAddin.csproj
@@ -99,7 +99,6 @@
-
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
index 42da189..5a2c881 100644
--- a/Properties/launchSettings.json
+++ b/Properties/launchSettings.json
@@ -2,7 +2,11 @@
"profiles": {
"LinkToolAddin": {
"commandName": "Executable",
- "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe"
+ "executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe",
+ "applicationUrl": "https://localhost:5001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
}
}
}
\ No newline at end of file
diff --git a/client/PythonMcpClient.cs b/client/StdioMcpClient.cs
similarity index 100%
rename from client/PythonMcpClient.cs
rename to client/StdioMcpClient.cs
diff --git a/client/prompt/DynamicPrompt.cs b/client/prompt/DynamicPrompt.cs
new file mode 100644
index 0000000..2a342e7
--- /dev/null
+++ b/client/prompt/DynamicPrompt.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace LinkToolAddin.client.prompt;
+
+public class DynamicPrompt
+{
+ public static string GetPrompt(string name,Dictionary args)
+ {
+ PromptTemplates promptTemplate = new PromptTemplates();
+ string template = promptTemplate.GetPrompt(name);
+ foreach (KeyValuePair pair in args)
+ {
+ string replaceKey = "{{"+pair.Key+"}}";
+ template.Replace(replaceKey, pair.Value.ToString());
+ }
+ return template;
+ }
+}
\ No newline at end of file
diff --git a/client/prompt/PromptTemplates.cs b/client/prompt/PromptTemplates.cs
new file mode 100644
index 0000000..3992482
--- /dev/null
+++ b/client/prompt/PromptTemplates.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+
+namespace LinkToolAddin.client.prompt;
+
+public class PromptTemplates
+{
+ private Dictionary prompts = new Dictionary();
+
+ public PromptTemplates()
+ {
+ prompts.Add("plan", "请根据用户所提问题进行工具规划");
+ prompts.Add("param", "根据帮助文档填写工具参数");
+ prompts.Add("code", "现在需要生成代码,要求语法正确");
+ }
+
+ public string GetPrompt(string name)
+ {
+ return prompts[name];
+ }
+}
\ No newline at end of file
diff --git a/host/Gateway.cs b/host/Gateway.cs
new file mode 100644
index 0000000..8237927
--- /dev/null
+++ b/host/Gateway.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using LinkToolAddin.client;
+using LinkToolAddin.client.prompt;
+using LinkToolAddin.host.llm;
+using LinkToolAddin.host.llm.entity;
+using LinkToolAddin.host.mcp;
+using LinkToolAddin.host.prompt;
+using LinkToolAddin.message;
+using ModelContextProtocol.Protocol.Types;
+using Newtonsoft.Json;
+
+namespace LinkToolAddin.host;
+
+public class Gateway
+{
+ public static async void SendMessage(string message, string model, string gdbPath, Action callback)
+ {
+ Llm bailian = new Bailian
+ {
+ api_key = "sk-db177155677e438f832860e7f4da6afc"
+ };
+ List messages = new List();
+ messages.Add(new Message
+ {
+ Role = "system",
+ Content = SystemPrompt.SysPromptTemplate
+ });
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = message
+ });
+ bool goOn = true;
+ string pattern = "[\\s\\S]*?<\\/tool_use>";
+ string promptPattern = "[\\s\\S]*?<\\/prompt>";
+ Dictionary servers = new Dictionary();
+ while (goOn)
+ {
+ string reponse = await bailian.SendChatAsync(new LlmJsonContent()
+ {
+ Model = model,
+ Messages = messages,
+ Temperature = 0.7,
+ TopP = 1,
+ MaxTokens = 1000,
+ });
+ messages.Add(new Message
+ {
+ Role = "assistant",
+ Content = reponse
+ });
+ if (Regex.IsMatch(reponse, pattern))
+ {
+ //工具类型的消息
+ XElement toolUse = XElement.Parse(reponse);
+ string fullToolName = toolUse.Element("name")?.Value;
+ string toolArgs = toolUse.Element("arguments")?.Value;
+ Dictionary toolParams = JsonConvert.DeserializeObject>(toolArgs);
+ string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName;
+ string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName;
+ McpServer mcpServer = servers[serverName];
+ if (mcpServer is SseMcpServer)
+ {
+ SseMcpServer sseMcpServer = mcpServer as SseMcpServer;
+ SseMcpClient client = new SseMcpClient(sseMcpServer.BaseUrl);
+ CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams);
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = toolResponse.IsError ? "fail" : "success",
+ content = toolResponse.Content.ToString()
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate
+ });
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = JsonConvert.SerializeObject(toolResponse)
+ });
+ callback?.Invoke(toolMessageItem);
+ }else if (mcpServer is StdioMcpServer)
+ {
+ StdioMcpServer stdioMcpServer = mcpServer as StdioMcpServer;
+ StdioMcpClient client = new StdioMcpClient(stdioMcpServer.Command, stdioMcpServer.Args);
+ CallToolResponse toolResponse = await client.CallToolAsync(toolName,toolParams);
+ MessageListItem toolMessageItem = new ToolMessageItem
+ {
+ toolName = toolName,
+ toolParams = toolParams,
+ type = MessageType.TOOL_MESSAGE,
+ status = toolResponse.IsError ? "fail" : "success",
+ content = toolResponse.Content.ToString()
+ };
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = toolResponse.IsError ? SystemPrompt.ErrorPromptTemplate : SystemPrompt.ContinuePromptTemplate
+ });
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = JsonConvert.SerializeObject(toolResponse)
+ });
+ callback?.Invoke(toolMessageItem);
+ }
+ }
+ else if (Regex.IsMatch(reponse, promptPattern))
+ {
+ XElement prompt = XElement.Parse(reponse);
+ string fullPromptName = prompt.Element("name")?.Value;
+ string promptArgs = prompt.Element("arguments")?.Value;
+ Dictionary promptParams = JsonConvert.DeserializeObject>(promptArgs);
+ string serverName = fullPromptName.Contains(":") ? fullPromptName.Split(':')[0] : fullPromptName;
+ string promptName = fullPromptName.Contains(":") ? fullPromptName.Split(':')[1] : fullPromptName;
+ string promptRes = DynamicPrompt.GetPrompt(promptName, promptParams);
+ messages.Add(new Message
+ {
+ Role = "user",
+ Content = promptRes
+ });
+ }
+ else
+ {
+ MessageListItem chatMessageListItem = new ChatMessageItem()
+ {
+ content = reponse,
+ role = "assistant",
+ type = MessageType.CHAT_MESSAGE
+ };
+ callback?.Invoke(chatMessageListItem);
+ }
+ if (reponse == "[DONE]")
+ {
+ goOn = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/host/ToolRequest.cs b/host/ToolRequest.cs
new file mode 100644
index 0000000..944026a
--- /dev/null
+++ b/host/ToolRequest.cs
@@ -0,0 +1,6 @@
+namespace LinkToolAddin.host;
+
+public class ToolRequest
+{
+
+}
\ No newline at end of file
diff --git a/host/mcp/McpServer.cs b/host/mcp/McpServer.cs
new file mode 100644
index 0000000..616798c
--- /dev/null
+++ b/host/mcp/McpServer.cs
@@ -0,0 +1,24 @@
+namespace LinkToolAddin.host.mcp
+{
+ using System;
+ using System.Collections.Generic;
+
+ using System.Globalization;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Converters;
+
+ public partial class McpServer
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("type")]
+ public string Type { get; set; }
+
+ [JsonProperty("description")]
+ public string Description { get; set; }
+
+ [JsonProperty("isActive")]
+ public bool IsActive { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/host/mcp/SseMcpServer.cs b/host/mcp/SseMcpServer.cs
new file mode 100644
index 0000000..cd2525e
--- /dev/null
+++ b/host/mcp/SseMcpServer.cs
@@ -0,0 +1,30 @@
+namespace LinkToolAddin.host.mcp
+{
+ using System;
+ using System.Collections.Generic;
+
+ using System.Globalization;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Converters;
+
+ public partial class SseMcpServer : McpServer
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("type")]
+ public string Type { get; set; }
+
+ [JsonProperty("description")]
+ public string Description { get; set; }
+
+ [JsonProperty("isActive")]
+ public bool IsActive { get; set; }
+
+ [JsonProperty("baseUrl")]
+ public string BaseUrl { get; set; }
+
+ [JsonProperty("headers")]
+ public Dictionary Headers { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/host/mcp/StdioMcpServer.cs b/host/mcp/StdioMcpServer.cs
new file mode 100644
index 0000000..a70ce40
--- /dev/null
+++ b/host/mcp/StdioMcpServer.cs
@@ -0,0 +1,30 @@
+namespace LinkToolAddin.host.mcp
+{
+ using System;
+ using System.Collections.Generic;
+
+ using System.Globalization;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Converters;
+
+ public partial class StdioMcpServer : McpServer
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("type")]
+ public string Type { get; set; }
+
+ [JsonProperty("isActive")]
+ public bool IsActive { get; set; }
+
+ [JsonProperty("registryUrl")]
+ public string RegistryUrl { get; set; }
+
+ [JsonProperty("command")]
+ public string Command { get; set; }
+
+ [JsonProperty("args")]
+ public List Args { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/host/prompt/SystemPrompt.cs b/host/prompt/SystemPrompt.cs
new file mode 100644
index 0000000..bcd3768
--- /dev/null
+++ b/host/prompt/SystemPrompt.cs
@@ -0,0 +1,11 @@
+namespace LinkToolAddin.host.prompt;
+
+public class SystemPrompt
+{
+ public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家,请以此身份回答用户的问题。";
+
+ public static string ContinuePromptTemplate = "上一个工具执行的结果如下,请据此继续执行";
+
+ public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,请根据报错信息重试";
+
+}
\ No newline at end of file
diff --git a/host/prompt/UserPrompt.cs b/host/prompt/UserPrompt.cs
new file mode 100644
index 0000000..128de5b
--- /dev/null
+++ b/host/prompt/UserPrompt.cs
@@ -0,0 +1,6 @@
+namespace LinkToolAddin.host.prompt;
+
+public class UserPrompt
+{
+
+}
\ No newline at end of file
diff --git a/server/ArcGISProMcpServer.cs b/server/ArcGISProMcpServer.cs
new file mode 100644
index 0000000..7aa94f7
--- /dev/null
+++ b/server/ArcGISProMcpServer.cs
@@ -0,0 +1,37 @@
+using System;
+using System.ComponentModel;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using ModelContextProtocol.Server;
+
+namespace LinkToolAddin.server;
+
+public class ArcGISProMcpServer
+{
+ public static async void TestMcpServer()
+ {
+ var builder = WebApplication.CreateBuilder();
+ builder.Logging.AddConsole(options =>
+ {
+ options.LogToStandardErrorThreshold = LogLevel.Trace;
+ });;
+ builder.Services.AddMcpServer().WithHttpTransport().WithToolsFromAssembly();
+ var app = builder.Build();
+ app.MapMcp("/sse");
+ app.MapGet("/", () => "MCP Server is running!");
+ Console.WriteLine("About to start server...");
+ await app.RunAsync();
+ }
+}
+
+[McpServerToolType]
+public static class EchoTool
+{
+ [McpServerTool, Description("Echoes the message back to the client.")]
+ public static string Echo(string message)
+ {
+ Console.WriteLine($"received message: {message}");
+ return $"hello {message}";
+ }
+}
\ No newline at end of file
diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs
index 3487472..aa2ea7f 100644
--- a/ui/dockpane/DialogDockpane.xaml.cs
+++ b/ui/dockpane/DialogDockpane.xaml.cs
@@ -17,6 +17,7 @@ using LinkToolAddin.client;
using LinkToolAddin.host.llm;
using LinkToolAddin.host.llm.entity;
using LinkToolAddin.resource;
+using LinkToolAddin.server;
using log4net;
using log4net.Appender;
using log4net.Config;
@@ -40,72 +41,10 @@ namespace LinkToolAddin.ui.dockpane
InitLogger();
InitializeComponent();
}
-
- public void CallBack(string str,object obj)
- {
- log.Info($"CallBack {str}");
- }
private async void TestServer_OnClick(object sender, RoutedEventArgs e)
{
log.Info("TestServer Clicked");
- List args = new List();
- args.Add("mcp-server-time");
- args.Add("--local-timezone=America/New_York");
- McpClient stdioMcpClient = new StdioMcpClient("uvx",args);
- IList tools = await stdioMcpClient.GetToolListAsync();
- foreach (McpClientTool tool in tools)
- {
- log.Info(tool.JsonSchema.ToString());
- }
- CallToolResponse response = await stdioMcpClient.CallToolAsync("get_current_time",
- new Dictionary { { "timezone", "America/New_York" } });
- log.Info(JsonConvert.SerializeObject(response));
- }
-
- private async void SseMcp_test()
- {
- SseMcpClient client = new SseMcpClient("https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2");
- IList tools = await client.GetToolListAsync();
- foreach (McpClientTool tool in tools)
- {
- log.Info(tool.JsonSchema.ToString());
- }
- }
-
- private async void Retrieve_Test()
- {
- log.Info("TestServer Clicked");
- // string jsonRpcString = @"{""jsonrpc"":""2.0"",""method"":""CallArcGISPro.CallArcGISProTool"",""params"":{""toolName"":""analysis.Buffer"",""toolParams"":""[\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流\"",\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流buffer\"",\""100\""]""},""id"":1}";
- DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProHelpDoc);
- string query = "缓冲区";
- KnowledgeResult knowledgeResult = await docDb.Retrieve(query);
- log.Info(JsonConvert.SerializeObject(knowledgeResult.ChunkList));
- }
-
- private async void Request_Bailian_Test()
- {
- Llm bailian = new Bailian
- {
- api_key = "sk-db177155677e438f832860e7f4da6afc",
- app_id = "6a77c5a68de64f469b79fcdcde9d5001",
- };
- string reponse = await bailian.SendChatAsync(new LlmJsonContent()
- {
- Model = "qwen-max",
- Messages = new List()
- {
- new Message()
- {
- Role = "user",
- Content = "你是谁"
- }
- },
- Temperature = 0.7,
- TopP = 1,
- MaxTokens = 1000,
- });
- log.Info(reponse);
}
protected void InitLogger()
@@ -121,7 +60,7 @@ namespace LinkToolAddin.ui.dockpane
// 2. 创建文件滚动输出器(按大小滚动)
var fileAppender = new RollingFileAppender
{
- File = Path.Combine("Logs", "linktool_app.log"), // 日志文件路径
+ File = Path.Combine("Logs", "D:\\linktool_app.log"), // 日志文件路径
AppendToFile = true, // 追加模式
RollingStyle = RollingFileAppender.RollingMode.Size, // 按文件大小滚动
MaxSizeRollBackups = 10, // 保留 10 个历史文件
diff --git a/ui/dockpane/TestDockpane.xaml b/ui/dockpane/TestDockpane.xaml
new file mode 100644
index 0000000..4782989
--- /dev/null
+++ b/ui/dockpane/TestDockpane.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/dockpane/TestDockpane.xaml.cs b/ui/dockpane/TestDockpane.xaml.cs
new file mode 100644
index 0000000..6f33bb6
--- /dev/null
+++ b/ui/dockpane/TestDockpane.xaml.cs
@@ -0,0 +1,157 @@
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using LinkToolAddin.client;
+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 Newtonsoft.Json;
+
+
+namespace LinkToolAddin.ui.dockpane
+{
+ ///
+ /// Interaction logic for TestDockpaneView.xaml
+ ///
+ public partial class TestDockpaneView : UserControl
+ {
+ private static ILog log = LogManager.GetLogger(typeof(TestDockpaneView));
+
+ public TestDockpaneView()
+ {
+ InitLogger();
+ InitializeComponent();
+ }
+
+ 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 = System.IO.Path.Combine("Logs", "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 日志(严重问题)");
+ }
+
+ public void CallBack(string str,object obj)
+ {
+ log.Info($"CallBack {str}");
+ }
+
+ private async void TestServer_OnClick(object sender, RoutedEventArgs e)
+ {
+ log.Info("TestServer Clicked");
+ ArcGISProMcpServer.TestMcpServer();
+ }
+
+ private async void StdioMcp_test()
+ {
+ List args = new List();
+ args.Add("mcp-server-time");
+ args.Add("--local-timezone=America/New_York");
+ McpClient stdioMcpClient = new StdioMcpClient("uvx",args);
+ IList tools = await stdioMcpClient.GetToolListAsync();
+ foreach (McpClientTool tool in tools)
+ {
+ log.Info(tool.JsonSchema.ToString());
+ }
+ CallToolResponse response = await stdioMcpClient.CallToolAsync("get_current_time",
+ new Dictionary { { "timezone", "America/New_York" } });
+ log.Info(JsonConvert.SerializeObject(response));
+ }
+
+ private async void SseMcp_test()
+ {
+ SseMcpClient client = new SseMcpClient("https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2");
+ IList tools = await client.GetToolListAsync();
+ foreach (McpClientTool tool in tools)
+ {
+ log.Info(tool.JsonSchema.ToString());
+ }
+ }
+
+ private async void Retrieve_Test()
+ {
+ log.Info("TestServer Clicked");
+ // string jsonRpcString = @"{""jsonrpc"":""2.0"",""method"":""CallArcGISPro.CallArcGISProTool"",""params"":{""toolName"":""analysis.Buffer"",""toolParams"":""[\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流\"",\""D:/01_Development/02_ArcGIS_Pro_Project/20250319_GisAi/Test.gdb/河流buffer\"",\""100\""]""},""id"":1}";
+ DocDb docDb = new DocDb("sk-db177155677e438f832860e7f4da6afc", DocDb.KnowledgeBase.ArcGISProHelpDoc);
+ string query = "缓冲区";
+ KnowledgeResult knowledgeResult = await docDb.Retrieve(query);
+ log.Info(JsonConvert.SerializeObject(knowledgeResult.ChunkList));
+ }
+
+ private async void Request_Bailian_Test()
+ {
+ Llm bailian = new Bailian
+ {
+ api_key = "sk-db177155677e438f832860e7f4da6afc",
+ app_id = "6a77c5a68de64f469b79fcdcde9d5001",
+ };
+ string reponse = await bailian.SendChatAsync(new LlmJsonContent()
+ {
+ Model = "qwen-max",
+ Messages = new List()
+ {
+ new Message()
+ {
+ Role = "user",
+ Content = "你是谁"
+ }
+ },
+ Temperature = 0.7,
+ TopP = 1,
+ MaxTokens = 1000,
+ });
+ log.Info(reponse);
+ }
+
+ private void TestButton_OnClick(object sender, RoutedEventArgs e)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ private void TestWorkflow_OnClick(object sender, RoutedEventArgs e)
+ {
+ Gateway.SendMessage("你好","qwen-max","test.gdb",ShowMessage);
+ }
+
+ public void ShowMessage(MessageListItem msg)
+ {
+ log.Info(msg.content);
+ }
+ }
+}
diff --git a/ui/dockpane/TestDockpaneViewModel.cs b/ui/dockpane/TestDockpaneViewModel.cs
new file mode 100644
index 0000000..0661d9c
--- /dev/null
+++ b/ui/dockpane/TestDockpaneViewModel.cs
@@ -0,0 +1,62 @@
+using ArcGIS.Core.CIM;
+using ArcGIS.Core.Data;
+using ArcGIS.Core.Geometry;
+using ArcGIS.Desktop.Catalog;
+using ArcGIS.Desktop.Core;
+using ArcGIS.Desktop.Editing;
+using ArcGIS.Desktop.Extensions;
+using ArcGIS.Desktop.Framework;
+using ArcGIS.Desktop.Framework.Contracts;
+using ArcGIS.Desktop.Framework.Dialogs;
+using ArcGIS.Desktop.Framework.Threading.Tasks;
+using ArcGIS.Desktop.KnowledgeGraph;
+using ArcGIS.Desktop.Layouts;
+using ArcGIS.Desktop.Mapping;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LinkToolAddin.ui.dockpane
+{
+ internal class TestDockpaneViewModel : DockPane
+ {
+ private const string _dockPaneID = "LinkToolAddin_ui_dockpane_TestDockpane";
+
+ protected TestDockpaneViewModel() { }
+
+ ///
+ /// Show the DockPane.
+ ///
+ internal static void Show()
+ {
+ DockPane pane = FrameworkApplication.DockPaneManager.Find(_dockPaneID);
+ if (pane == null)
+ return;
+
+ pane.Activate();
+ }
+
+ ///
+ /// Text shown near the top of the DockPane.
+ ///
+ private string _heading = "My DockPane";
+ public string Heading
+ {
+ get => _heading;
+ set => SetProperty(ref _heading, value);
+ }
+ }
+
+ ///
+ /// Button implementation to show the DockPane.
+ ///
+ internal class TestDockpane_ShowButton : Button
+ {
+ protected override void OnClick()
+ {
+ TestDockpaneViewModel.Show();
+ }
+ }
+}
diff --git a/ui/message/ChatMessageItem.cs b/ui/message/ChatMessageItem.cs
new file mode 100644
index 0000000..2c35847
--- /dev/null
+++ b/ui/message/ChatMessageItem.cs
@@ -0,0 +1,9 @@
+namespace LinkToolAddin.message;
+
+public class ChatMessageItem : MessageListItem
+{
+ public string id { get; set; }
+ public string role { get; set; }
+ public string content { get; set; }
+ public MessageType type { get; set; }
+}
\ No newline at end of file
diff --git a/ui/message/MessageListItem.cs b/ui/message/MessageListItem.cs
index 0de3b34..1c58cbd 100644
--- a/ui/message/MessageListItem.cs
+++ b/ui/message/MessageListItem.cs
@@ -1,7 +1,15 @@
namespace LinkToolAddin.message;
+public enum MessageType
+{
+ TOOL_MESSAGE,
+ CHAT_MESSAGE,
+}
+
public interface MessageListItem
{
+ string id { get; set; }
string role { get; set; }
string content { get; set; }
+ MessageType type { get; set; }
}
\ No newline at end of file
diff --git a/ui/message/ToolMessageItem.cs b/ui/message/ToolMessageItem.cs
new file mode 100644
index 0000000..211a4db
--- /dev/null
+++ b/ui/message/ToolMessageItem.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace LinkToolAddin.message;
+
+public class ToolMessageItem : MessageListItem
+{
+ public string id { get; set; }
+ public string role { get; set; }
+ public string content { get; set; }
+ public string toolName { get; set; }
+ public Dictionary toolParams { get; set; }
+ public MessageType type { get; set; }
+ public string status { get; set; }
+ public string result { get; set; }
+}
\ No newline at end of file