The tool executes correctly and is displayed in the chat correctly.

This commit is contained in:
Peer Schütt 2026-04-13 10:58:43 +02:00
parent 3b23a4b5b2
commit 25cc7275f6
5 changed files with 105 additions and 65 deletions

View File

@ -1705,6 +1705,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1564757972"] = "Execute
-- Yes, regenerate it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it"
-- No result
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1684269223"] = "No result"
-- Yes, remove it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it"

View File

@ -11,9 +11,27 @@
</MudAvatar>
</CardHeaderAvatar>
<CardHeaderContent>
<MudText Typo="Typo.body1">
@this.Role.ToName() (@this.Time.LocalDateTime)
</MudText>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudText Typo="Typo.body1">
@this.Role.ToName() (@this.Time.LocalDateTime)
</MudText>
@if (this.HasToolTrace)
{
<MudTooltip Text="@this.GetToolTraceTooltip()" Placement="Placement.Bottom">
<MudButton Variant="Variant.Outlined"
Color="Color.Default"
Size="Size.Small"
Class="px-2 py-1 rounded-pill"
Style="min-width:auto; border-width:1px; text-transform:none;"
OnClick="@this.ToggleToolTrace">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIcon Icon="@Icons.Material.Filled.Build" Color="Color.Default" Size="Size.Small" />
<MudIcon Icon="@(this.showToolTrace ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)" Size="Size.Small" />
</MudStack>
</MudButton>
</MudTooltip>
}
</MudStack>
</CardHeaderContent>
<CardHeaderActions>
@if (this.Content.FileAttachments.Count > 0)
@ -96,17 +114,64 @@
}
else
{
@if (this.Role is ChatRole.AI && textContent.ToolInvocations.Count > 0)
@if (this.HasToolTrace && this.showToolTrace)
{
<MudPaper Class="d-flex align-center justify-space-between px-3 py-2 mb-3 border rounded-lg" Style="gap: 8px;">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
@foreach (var toolIcon in this.GetDistinctToolIcons())
{
<MudIcon Icon="@toolIcon" Color="Color.Info" />
}
<MudText Typo="Typo.body2">@this.GetToolTraceTooltip()</MudText>
</MudStack>
<MudIconButton Icon="@(this.showToolTrace ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)" Size="Size.Small" OnClick="@this.ToggleToolTrace" />
<MudPaper Class="pa-3 mb-3 border rounded-lg" Style="border-width:1px;">
<MudText Typo="Typo.subtitle2" Class="mb-2">
@string.Format(T("Tool Calls ({0})"), textContent.ToolInvocations.Count)
</MudText>
@foreach (var invocation in textContent.ToolInvocations.OrderBy(x => x.Order))
{
<MudPaper Class="pa-3 mb-3 border rounded-lg" Style="border-width:1px;">
<MudButton Variant="Variant.Text"
Color="Color.Default"
FullWidth="@true"
Class="px-0 py-0 justify-space-between"
Style="min-width:auto; text-transform:none;"
OnClick="@(() => this.ToggleToolInvocation(invocation.Order))">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="w-100">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@invocation.ToolIcon" Color="Color.Info" />
<MudText Typo="Typo.subtitle1">@($"{invocation.Order}. {invocation.ToolName}")</MudText>
<MudChip T="string" Color="@ContentBlockComponent.GetTraceColor(invocation.Status)" Size="Size.Small" Variant="Variant.Outlined">
@this.GetTraceStatusText(invocation)
</MudChip>
</MudStack>
<MudIcon Icon="@(this.IsToolInvocationExpanded(invocation.Order) ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)" Size="Size.Small" />
</MudStack>
</MudButton>
@if (this.IsToolInvocationExpanded(invocation.Order))
{
@if (!string.IsNullOrWhiteSpace(invocation.StatusMessage))
{
<MudText Typo="Typo.body2" Color="Color.Warning" Class="mt-3 mb-3">@invocation.StatusMessage</MudText>
}
<MudText Typo="Typo.subtitle2">@T("Result")</MudText>
<MudPaper Class="pa-3 mt-2 mb-3">
<MudText Typo="Typo.body2" Style="white-space: pre-wrap;">@this.GetToolInvocationResult(invocation)</MudText>
</MudPaper>
<MudText Typo="Typo.subtitle2">@T("Arguments")</MudText>
@if (invocation.Arguments.Count == 0)
{
<MudText Typo="Typo.body2" Class="mb-3">@T("No arguments")</MudText>
}
else
{
<MudList T="string" Dense="@true" Class="mb-0">
@foreach (var argument in invocation.Arguments)
{
<MudListItem T="string">
<MudText Typo="Typo.body2"><strong>@argument.Key:</strong> @argument.Value</MudText>
</MudListItem>
}
</MudList>
}
}
</MudPaper>
}
</MudPaper>
}
@ -136,52 +201,6 @@
@textContent.ToolRuntimeStatus.Message
</MudAlert>
}
@if (this.Role is ChatRole.AI && textContent.ToolInvocations.Count > 0 && this.showToolTrace)
{
<MudText Typo="Typo.subtitle2" Class="mb-2">
@string.Format(T("Tool Calls ({0})"), textContent.ToolInvocations.Count)
</MudText>
@foreach (var invocation in textContent.ToolInvocations.OrderBy(x => x.Order))
{
<MudPaper Class="pa-3 mb-3 border rounded-lg">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-3">
<MudIcon Icon="@invocation.ToolIcon" Color="Color.Info" />
<MudText Typo="Typo.subtitle1">@($"{invocation.Order}. {invocation.ToolName}")</MudText>
<MudChip T="string" Color="@ContentBlockComponent.GetTraceColor(invocation.Status)" Size="Size.Small" Variant="Variant.Outlined">
@this.GetTraceStatusText(invocation)
</MudChip>
</MudStack>
@if (!string.IsNullOrWhiteSpace(invocation.StatusMessage))
{
<MudText Typo="Typo.body2" Color="Color.Warning" Class="mb-3">@invocation.StatusMessage</MudText>
}
<MudText Typo="Typo.subtitle2">@T("Arguments")</MudText>
@if (invocation.Arguments.Count == 0)
{
<MudText Typo="Typo.body2" Class="mb-3">@T("No arguments")</MudText>
}
else
{
<MudList T="string" Dense="@true" Class="mb-3">
@foreach (var argument in invocation.Arguments)
{
<MudListItem T="string">
<MudText Typo="Typo.body2"><strong>@argument.Key:</strong> @argument.Value</MudText>
</MudListItem>
}
</MudList>
}
<MudText Typo="Typo.subtitle2">@T("Result")</MudText>
<MudPaper Class="pa-3 mt-2">
<MudText Typo="Typo.body2" Style="white-space: pre-wrap;">@invocation.Result</MudText>
</MudPaper>
</MudPaper>
}
}
}
}
}

View File

@ -3,6 +3,7 @@ using AIStudio.Dialogs;
using AIStudio.Tools.Services;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace AIStudio.Chat;
@ -105,6 +106,7 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private bool hasActiveMathContainer;
private bool isDisposed;
private bool showToolTrace;
private readonly HashSet<int> expandedToolInvocations = [];
#region Overrides of ComponentBase
@ -205,6 +207,9 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
hash.Add(text.ToolRuntimeStatus.IsRunning);
hash.Add(text.ToolRuntimeStatus.Message);
hash.Add(this.showToolTrace);
hash.Add(this.expandedToolInvocations.Count);
foreach (var expandedInvocation in this.expandedToolInvocations.Order())
hash.Add(expandedInvocation);
foreach (var invocation in text.ToolInvocations)
{
hash.Add(invocation.Order);
@ -234,6 +239,8 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private string CardClasses => $"my-2 rounded-lg {this.Class}";
private bool HasToolTrace => this.Role is ChatRole.AI && this.GetToolInvocations().Count > 0;
private CodeBlockTheme CodeColorPalette => this.SettingsManager.IsDarkMode ? CodeBlockTheme.Dark : CodeBlockTheme.Default;
private static Color GetTraceColor(ToolInvocationTraceStatus status) => status switch
@ -256,11 +263,6 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
? textContent.ToolInvocations.OrderBy(x => x.Order).ToList()
: [];
private IReadOnlyList<string> GetDistinctToolIcons() => this.GetToolInvocations()
.Select(x => x.ToolIcon)
.Distinct(StringComparer.Ordinal)
.ToList();
private string GetToolTraceTooltip()
{
var invocations = this.GetToolInvocations();
@ -274,6 +276,18 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private void ToggleToolTrace() => this.showToolTrace = !this.showToolTrace;
private bool IsToolInvocationExpanded(int order) => this.expandedToolInvocations.Contains(order);
private void ToggleToolInvocation(int order)
{
if (!this.expandedToolInvocations.Add(order))
this.expandedToolInvocations.Remove(order);
}
private string GetToolInvocationResult(ToolInvocationTrace invocation) => string.IsNullOrWhiteSpace(invocation.Result)
? this.T("No result")
: invocation.Result;
private MudMarkdownStyling MarkdownStyling => new()
{
CodeBlock = { Theme = this.CodeColorPalette },

View File

@ -8,5 +8,5 @@
{
<ConfigurationOption OptionDescription="@T("Show tool selection in this assistant?")" LabelOn="@T("Tool selection is visible")" LabelOff="@T("Tool selection is hidden")" State="@(() => this.SettingsManager.IsToolSelectionVisible(this.Component))" StateUpdate="@(value => this.SettingsManager.SetToolSelectionVisibility(this.Component, value))" />
}
<ConfigurationMultiSelect TData="string" OptionDescription="@this.OptionTitle" SelectedValues="@this.GetSelectedValues" Data="@this.availableTools" SelectionUpdate="@this.UpdateSelection" OptionHelp="@this.OptionHelp" EmptySelectionText="@T("No tools selected.")" SingleSelectionText="@T("You have selected 1 tool.")" MultipleSelectionText="@T("You have selected {0} tools.")" />
<ConfigurationMultiSelect TData="string" OptionDescription="@this.OptionTitle" SelectedValues="@this.GetSelectedValues" Data="@this.availableTools" SelectionUpdate="@this.UpdateSelection" OptionHelp="@this.OptionHelp" Disabled="@(() => this.AreDefaultToolsDisabled)" EmptySelectionText="@T("No tools selected.")" SingleSelectionText="@T("You have selected 1 tool.")" MultipleSelectionText="@T("You have selected {0} tools.")" />
}

View File

@ -25,6 +25,10 @@ public partial class ToolDefaultsConfiguration : MSGComponentBase
? this.T("Choose which tools should be preselected for new chats.")
: this.T("Choose which tools should be preselected for new runs of this assistant.");
private bool AreDefaultToolsDisabled =>
this.Component is not AIStudio.Tools.Components.CHAT &&
!this.SettingsManager.IsToolSelectionVisible(this.Component);
protected override async Task OnInitializedAsync()
{
this.availableTools = (await this.ToolRegistry.GetCatalogAsync(this.Component))