diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index d764cc6..bece90c 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -132,6 +132,14 @@ Always + + + Always + + + + Always + diff --git a/host/Gateway.cs b/host/Gateway.cs index 63ef2b2..a2af774 100644 --- a/host/Gateway.cs +++ b/host/Gateway.cs @@ -282,12 +282,28 @@ public class Gateway { //如果本次回复不包含任何工具的调用或提示词的调用,则不再请求 goOn = false; + MessageListItem endMessageListItem1 = new ChatMessageItem + { + type = MessageType.END_TAG, + content = "", + id = (timestamp+3).ToString(), + role = "assistant" + }; + callback?.Invoke(endMessageListItem1); break; } await foreach(LlmStreamChat llmStreamChat in bailian.SendChatStreamAsync(jsonContent)) { if (!goOn) { + MessageListItem endMessageListItem2 = new ChatMessageItem + { + type = MessageType.END_TAG, + content = "", + id = (timestamp+3).ToString(), + role = "assistant" + }; + callback?.Invoke(endMessageListItem2); break; } @@ -299,7 +315,7 @@ public class Gateway content = llmStreamChat.Choices[0].Delta.ResoningContent, role = "assistant", type = MessageType.REASON_MESSAGE, - id = (timestamp-1).ToString() + id = (timestamp+2).ToString() }; Application.Current.Dispatcher.Invoke(() => { @@ -328,17 +344,14 @@ public class Gateway else { //包含Prompt调用请求的消息 - if (remainingPrompt != "") + MessageListItem chatMessageListItem = new ChatMessageItem() { - MessageListItem chatMessageListItem = new ChatMessageItem() - { - content = remainingPrompt, - role = "assistant", - type = MessageType.CHAT_MESSAGE, - id = timestamp.ToString() - }; - callback?.Invoke(chatMessageListItem); - } + 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; @@ -355,20 +368,6 @@ public class Gateway else { //包含工具调用请求的消息 - if (remaining != "") - { - 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; @@ -377,6 +376,32 @@ public class Gateway string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName; McpServer mcpServer = mcpServerList.GetServer(serverName); //将工具调用请求添加至列表中待一次回答完整后再统一进行调用 + MessageListItem chatMessageListItem = new ChatMessageItem() + { + content = remaining, + role = "assistant", + type = MessageType.CHAT_MESSAGE, + id = timestamp.ToString() + }; + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(chatMessageListItem); + }); + MessageListItem toolMessageListItem = new ToolMessageItem() + { + content = "", + result = "", + toolName = toolName, + toolParams = toolParams, + role = "user", + type = MessageType.TOOL_MESSAGE, + id = (timestamp+1).ToString(), + status = "loading" + }; + Application.Current.Dispatcher.Invoke(() => + { + callback?.Invoke(toolMessageListItem); + }); mcpToolRequests = new List(); McpToolRequest mcpToolRequest = new McpToolRequest() { @@ -421,6 +446,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), + result = JsonConvert.SerializeObject(toolResponse), id = (timestamp + 1).ToString(), role = "user" }; @@ -448,6 +474,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = toolResponse.IsError ? "fail" : "success", content = JsonConvert.SerializeObject(toolResponse), + result = JsonConvert.SerializeObject(toolResponse), id = (timestamp + 1).ToString(), role = "user" }; @@ -494,6 +521,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "fail", content = JsonConvert.SerializeObject(innerResult), + result = JsonConvert.SerializeObject(innerResult), id = (timestamp + 1).ToString(), role = "user" }; @@ -516,6 +544,7 @@ public class Gateway type = MessageType.TOOL_MESSAGE, status = "success", content = JsonConvert.SerializeObject(innerResult), + result = JsonConvert.SerializeObject(innerResult), id = (timestamp + 1).ToString(), role = "user" }; @@ -569,6 +598,14 @@ public class Gateway log.Error(e.Message); } } + MessageListItem endMessageListItem = new ChatMessageItem + { + type = MessageType.END_TAG, + content = "", + id = (timestamp+3).ToString(), + role = "assistant" + }; + callback?.Invoke(endMessageListItem); } } diff --git a/resource/img/fold.png b/resource/img/fold.png new file mode 100644 index 0000000..2690b19 Binary files /dev/null and b/resource/img/fold.png differ diff --git a/resource/img/unfold.png b/resource/img/unfold.png new file mode 100644 index 0000000..749e87e Binary files /dev/null and b/resource/img/unfold.png differ diff --git a/ui/VersionButton.cs b/ui/VersionButton.cs index 62e157d..bc4df57 100644 --- a/ui/VersionButton.cs +++ b/ui/VersionButton.cs @@ -22,7 +22,7 @@ namespace LinkToolAddin { internal class VersionButton : Button { - private string version = "0.1.0"; + private string version = "0.1.1"; protected override void OnClick() { MessageBox.Show($"当前LinkTool版本为{version}", "版本信息"); diff --git a/ui/dockpane/DialogDockpane.xaml b/ui/dockpane/DialogDockpane.xaml index fafe132..d232eaa 100644 --- a/ui/dockpane/DialogDockpane.xaml +++ b/ui/dockpane/DialogDockpane.xaml @@ -25,18 +25,30 @@ - + + + + + + + + + + + + + - + - + - + diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 1148b88..d11083d 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -117,13 +117,17 @@ namespace LinkToolAddin.ui.dockpane QuestionTextbox.Text = ""; borderItemsDict[timestamp.ToString()] = userMsgBoder; ChatHistoryStackPanel.Children.Add(userMsgBoder); - Gateway.SendMessageStream(question,"qwen-max",defaultGdbPath,NewMessage_Recall); + 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中 @@ -147,17 +151,32 @@ namespace LinkToolAddin.ui.dockpane ChatHistoryStackPanel.Children.Add(border); } } - else + else if(msg.role == "assistant") { - Border border = GetAiChatBorder(msg); - borderItemsDict[msgId] = border; - ChatHistoryStackPanel.Children.Add(border); + 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) @@ -166,22 +185,42 @@ namespace LinkToolAddin.ui.dockpane 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 { - Border borderItem = borderItemsDict[msgId]; - Grid grid = borderItem.Child as Grid; - TextBox textBox = grid.Children[1] as TextBox; - textBox.Text = msg.content; + 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) @@ -204,6 +243,8 @@ namespace LinkToolAddin.ui.dockpane 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; @@ -211,6 +252,105 @@ namespace LinkToolAddin.ui.dockpane 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(); @@ -227,11 +367,14 @@ namespace LinkToolAddin.ui.dockpane 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; @@ -245,6 +388,7 @@ namespace LinkToolAddin.ui.dockpane 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() @@ -256,17 +400,46 @@ namespace LinkToolAddin.ui.dockpane 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(); - textBlock.Text = (msg as ToolMessageItem).toolName; + 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(); @@ -282,5 +455,29 @@ namespace LinkToolAddin.ui.dockpane }; 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(); + } } } diff --git a/ui/message/MessageListItem.cs b/ui/message/MessageListItem.cs index 9b97709..3b0ce37 100644 --- a/ui/message/MessageListItem.cs +++ b/ui/message/MessageListItem.cs @@ -5,6 +5,7 @@ public enum MessageType TOOL_MESSAGE, CHAT_MESSAGE, REASON_MESSAGE, + END_TAG } public interface MessageListItem