Skip to content

Commit 1e5809b

Browse files
committed
More
1 parent 93dcb2e commit 1e5809b

File tree

8 files changed

+161
-37
lines changed

8 files changed

+161
-37
lines changed

playground/GitHubModelsEndToEnd/GitHubModelsEndToEnd.WebStory/Components/Pages/Home.razor

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@
2626
"Richard", "Susan", "Joseph", "Jessica", "Thomas", "Sarah",
2727
"Charles", "Karen"
2828
};
29+
private static readonly string[] s_plotTwists = [
30+
"The mentor is actually the villain in disguise.",
31+
"The protagonist’s memories were fabricated to hide the truth.",
32+
"The supposed enemy has been protecting the hero all along.",
33+
"A trusted ally betrays the group for personal gain.",
34+
"The artifact everyone seeks never existed — it was a myth to test them.",
35+
"The villain is revealed to be a future version of the protagonist.",
36+
"The world the characters live in is a simulation.",
37+
"The prophecy was mistranslated — the hero is not the savior but the destroyer.",
38+
"The character believed dead returns with no memory of their past.",
39+
"The conflict was orchestrated by a third unseen force manipulating both sides."
40+
];
2941

3042
private List<ChatMessage> chatMessages = new List<ChatMessage>
3143
{
@@ -42,11 +54,19 @@
4254
return Task.FromResult(names);
4355
}
4456

57+
public Task<string> GeneratePlotTwist()
58+
{
59+
return Task.FromResult(s_plotTwists[Random.Shared.Next(0, s_plotTwists.Length)]);
60+
}
61+
4562
private async Task GenerateNextParagraph()
4663
{
4764
var chatOptions = new ChatOptions
4865
{
49-
Tools = [AIFunctionFactory.Create(GenerateNamesAsync, name: "generate_names", description: "Generates a list of fictional names for the story.")]
66+
Tools = [
67+
AIFunctionFactory.Create(GenerateNamesAsync, name: "generate_names", description: "Generates a list of fictional names for the story."),
68+
AIFunctionFactory.Create(GeneratePlotTwist, name: "generate_plot_twist", description: "Generates a plot twist for the story.")
69+
]
5070
};
5171

5272
if (chatMessages.Count > 1)

src/Aspire.Dashboard/Components/Dialogs/GenAIVisualizerDialog.razor

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,39 @@
136136
{
137137
<TextVisualizer ViewModel="@itemPart.TextVisualizerViewModel" HideLineNumbers="true" Virtualize="false" />
138138
}
139+
140+
if (itemPart.MessagePart is ToolCallRequestPart toolCallPart && !string.IsNullOrEmpty(toolCallPart.Name))
141+
{
142+
if (Content.ToolDefinitions.FirstOrDefault(d => d.ToolDefinition.Name == toolCallPart.Name) is { } toolVM)
143+
{
144+
<div class="tool-button-container">
145+
<FluentButton OnClick="@(() => ViewToolDefinition(toolVM))">
146+
<FluentIcon Value="@s_wrenchIcon" Style="vertical-align: sub;" slot="start" />
147+
Tool definition
148+
</FluentButton>
149+
</div>
150+
}
151+
}
152+
else if (itemPart.MessagePart is ToolCallResponsePart toolCallResponsePart && !string.IsNullOrEmpty(toolCallResponsePart.Id))
153+
{
154+
if (TryGetToolCall(toolCallResponsePart.Id, out var itemVM, out var toolCallRequestPart))
155+
{
156+
<div class="tool-button-container">
157+
<FluentButton OnClick="@(() => OnViewItem(itemVM))">
158+
<FluentIcon Value="@s_toolIcon" Style="vertical-align: sub;" slot="start" />
159+
Tool call
160+
</FluentButton>
161+
162+
@if (Content.ToolDefinitions.FirstOrDefault(d => d.ToolDefinition.Name == toolCallRequestPart.Name) is { } toolVM)
163+
{
164+
<FluentButton OnClick="@(() => ViewToolDefinition(toolVM))">
165+
<FluentIcon Value="@s_wrenchIcon" Style="vertical-align: sub;" slot="start" />
166+
Tool definition
167+
</FluentButton>
168+
}
169+
</div>
170+
}
171+
}
139172
}
140173
}
141174
</div>
@@ -231,11 +264,11 @@
231264
@((MarkupString)string.Format(CultureInfo.CurrentCulture, Loc[nameof(Dialogs.GenAINoMessageContentMoreInformationMessage)], "https://aka.ms/aspire/telemetry-ai-content"))
232265
</div>
233266
}
234-
@RenderMessageSection(Loc[nameof(Dialogs.GenAIInputHeaderText)], Content.InputMessages, Content.NoMessageContent, Content.ToolDefinitions)
235-
@RenderMessageSection(Loc[nameof(Dialogs.GenAIOutputHeaderText)], Content.OutputMessages, Content.NoMessageContent, Content.ToolDefinitions)
267+
@RenderMessageSection(Loc[nameof(Dialogs.GenAIInputHeaderText)], Content.InputMessages, Content.NoMessageContent)
268+
@RenderMessageSection(Loc[nameof(Dialogs.GenAIOutputHeaderText)], Content.OutputMessages, Content.NoMessageContent)
236269
@if (Content.ErrorItem is { } errorItem)
237270
{
238-
@RenderMessageSection(Loc[nameof(Dialogs.GenAIErrorHeaderText)], [errorItem], Content.NoMessageContent, Content.ToolDefinitions)
271+
@RenderMessageSection(Loc[nameof(Dialogs.GenAIErrorHeaderText)], [errorItem], Content.NoMessageContent)
239272
}
240273
}
241274
</div>
@@ -252,23 +285,23 @@
252285
@if (Content.ToolDefinitions.Count > 0)
253286
{
254287
<FluentAccordion Class="tools-list">
255-
@foreach (var tool in Content.ToolDefinitions.Where(t => t.Type == "function"))
288+
@foreach (var toolVM in Content.ToolDefinitions.Where(t => t.ToolDefinition.Type == "function"))
256289
{
257-
<FluentAccordionItem title="test 4">
290+
<FluentAccordionItem @bind-Expanded="toolVM.Expanded">
258291
<HeadingTemplate>
259-
<div style="display: flex; align-items: center; gap: 1rem;">
292+
<div class="tool-heading">
260293
<FluentBadge Appearance="Appearance.Accent" Style="min-width: 80px; text-align: center;">
261-
@tool.Type
294+
@toolVM.ToolDefinition.Type
262295
</FluentBadge>
263-
<strong>@tool.Name</strong>
264-
@if (!string.IsNullOrEmpty(tool.Description))
296+
<strong>@toolVM.ToolDefinition.Name</strong>
297+
@if (!string.IsNullOrEmpty(toolVM.ToolDefinition.Description))
265298
{
266-
<span>@FormatHelpers.TruncateText(tool.Description, maxLength: 100)</span>
299+
<span>@FormatHelpers.TruncateText(toolVM.ToolDefinition.Description, maxLength: 100)</span>
267300
}
268301
</div>
269302
</HeadingTemplate>
270303
<ChildContent>
271-
@if (tool.Parameters?.Properties.Count > 0)
304+
@if (toolVM.ToolDefinition.Parameters?.Properties.Count > 0)
272305
{
273306
<table class="tool-parameters-table">
274307
<thead>
@@ -279,12 +312,12 @@
279312
</tr>
280313
</thead>
281314
<tbody>
282-
@foreach (var prop in tool.Parameters.Properties)
315+
@foreach (var prop in toolVM.ToolDefinition.Parameters.Properties)
283316
{
284317
<tr>
285318
<td class="tool-cell tool-cell-nowrap">
286319
<strong>@prop.Key</strong>
287-
@if (tool.Parameters.Required?.Contains(prop.Key) == true)
320+
@if (toolVM.ToolDefinition.Parameters.Required?.Contains(prop.Key) == true)
288321
{
289322
<span style="color: var(--error);"> *</span>
290323
}
@@ -299,16 +332,16 @@
299332
}
300333
</tbody>
301334
</table>
302-
@if (tool.Parameters.Required?.Count > 0)
335+
@if (toolVM.ToolDefinition.Parameters.Required?.Count > 0)
303336
{
304-
<div>
337+
<div class="tool-footer">
305338
<span style="color: var(--error);">*</span> Required parameter
306339
</div>
307340
}
308341
}
309342
else
310343
{
311-
<p style="font-style: italic; color: var(--neutral-foreground-rest);">No parameters</p>
344+
<div class="tool-footer">No parameters</div>
312345
}
313346
</ChildContent>
314347
</FluentAccordionItem>
@@ -331,7 +364,7 @@
331364
<FluentDialogFooter Visible="false" />
332365

333366
@{
334-
RenderFragment RenderMessageSection(string title, List<GenAIItemViewModel> items, bool noMessageContent, List<ToolDefinition> toolDefinitions)
367+
RenderFragment RenderMessageSection(string title, List<GenAIItemViewModel> items, bool noMessageContent)
335368
{
336369
return@<div class="section-container">
337370
<div class="section-title">@title</div>
@@ -358,14 +391,6 @@
358391
foreach (var itemPart in itemParts)
359392
{
360393
<TextVisualizer ViewModel="@itemPart.TextVisualizerViewModel" HideLineNumbers="true" Virtualize="false" />
361-
362-
@if (itemPart.MessagePart is ToolCallRequestPart toolCallPart && !string.IsNullOrEmpty(toolCallPart.Name))
363-
{
364-
if (toolDefinitions.FirstOrDefault(d => d.Name == toolCallPart.Name) is { } sdf)
365-
{
366-
<FluentButton Appearance="Appearance.Outline">View @toolCallPart.Name definition</FluentButton>
367-
}
368-
}
369394
}
370395
}
371396
else

src/Aspire.Dashboard/Components/Dialogs/GenAIVisualizerDialog.razor.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@
1313
using Microsoft.AspNetCore.Components;
1414
using Microsoft.Extensions.Localization;
1515
using Microsoft.FluentUI.AspNetCore.Components;
16+
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;
1617

1718
namespace Aspire.Dashboard.Components.Dialogs;
1819

1920
public partial class GenAIVisualizerDialog : ComponentBase, IDisposable
2021
{
22+
private static readonly Icon s_wrenchIcon = new Icons.Regular.Size16.Wrench();
23+
private static readonly Icon s_toolIcon = new Icons.Regular.Size16.Code();
24+
2125
private readonly string _copyButtonId = $"copy-{Guid.NewGuid():N}";
2226

2327
private MarkdownProcessor _markdownProcess = default!;
@@ -110,6 +114,33 @@ private void OnViewItem(GenAIItemViewModel viewModel)
110114
SelectedItem = viewModel;
111115
}
112116

117+
private void ViewToolDefinition(ToolDefinitionViewModel toolDefinition)
118+
{
119+
SelectedItem = null;
120+
OverviewActiveView = OverviewViewKind.Tools;
121+
toolDefinition.Expanded = true;
122+
}
123+
124+
private bool TryGetToolCall(string id, [NotNullWhen(true)] out GenAIItemViewModel? itemVM, [NotNullWhen(true)] out ToolCallRequestPart? toolCallRequestPart)
125+
{
126+
foreach (var messages in Content.InputMessages)
127+
{
128+
foreach (var part in messages.ItemParts)
129+
{
130+
if (part.MessagePart is ToolCallRequestPart { } p && p.Id == id)
131+
{
132+
itemVM = messages;
133+
toolCallRequestPart = p;
134+
return true;
135+
}
136+
}
137+
}
138+
139+
itemVM = null;
140+
toolCallRequestPart = null;
141+
return false;
142+
}
143+
113144
private Task HandleSelectedTreeItemChangedAsync()
114145
{
115146
var selectedIndex = Content.SelectedTreeItem?.Data as int?;

src/Aspire.Dashboard/Components/Dialogs/GenAIVisualizerDialog.razor.css

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
grid-template-rows: auto 1fr auto;
1010
}
1111

12-
::deep .span-messages-container fluent-button::part(control):not(:hover) {
12+
::deep .span-messages-container fluent-button.message-copy-button::part(control):not(:hover) {
1313
background: var(--main-container-background-color);
1414
}
1515

@@ -253,17 +253,42 @@
253253
--neutral-fill-stealth-rest-on-neutral-fill-layer-rest: var(--property-grid-genai-dialog-background-color);
254254
}
255255

256+
::deep .tab-container .tool-heading {
257+
display: flex;
258+
align-items: center;
259+
gap: 12px;
260+
padding-left: 4px;
261+
}
262+
263+
::deep .tab-container .tool-footer {
264+
padding-left: 4px;
265+
}
266+
256267
::deep .tab-container .tool-parameters-table {
257268
width: 100%;
258269
border-collapse: collapse;
259270
}
260271

272+
::deep .tab-container .tool-button-container {
273+
display: flex;
274+
gap: 8px;
275+
margin-top: 4px;
276+
}
277+
261278
::deep .tab-container .tool-cell {
262279
text-align: left;
263280
padding: 8px;
264281
border-bottom: 1px solid var(--neutral-stroke-divider-rest);
265282
}
266283

284+
::deep .tab-container strong {
285+
font-weight: 600;
286+
}
287+
288+
::deep .tab-container th.tool-cell {
289+
font-weight: 600;
290+
}
291+
267292
::deep .tab-container .tool-cell-nowrap {
268293
text-wrap: nowrap;
269294
}

src/Aspire.Dashboard/Model/GenAI/GenAIItemPartViewModel.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,15 @@ private static TextVisualizerViewModel CreateMessagePartVisualizer(MessagePart p
6363
}
6464
if (p is ToolCallRequestPart toolCallRequestPart)
6565
{
66-
return new TextVisualizerViewModel($"{toolCallRequestPart.Name}({toolCallRequestPart.Arguments?.ToJsonString()})", indentText: true, knownFormat: DashboardUIHelpers.JavascriptFormat);
66+
var argumentsText = toolCallRequestPart.Arguments switch
67+
{
68+
null => string.Empty,
69+
JsonObject obj when obj.Count == 0 => string.Empty,
70+
JsonArray arr when arr.Count == 0 => string.Empty,
71+
_ => toolCallRequestPart.Arguments.ToJsonString()
72+
};
73+
74+
return new TextVisualizerViewModel($"{toolCallRequestPart.Name}({argumentsText})", indentText: true, knownFormat: DashboardUIHelpers.JavascriptFormat);
6775
}
6876
if (p is ToolCallResponsePart toolCallResponsePart)
6977
{

src/Aspire.Dashboard/Model/GenAI/GenAIMessages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
45
using System.Text.Json;
56
using System.Text.Json.Nodes;
67
using System.Text.Json.Serialization;
@@ -87,6 +88,7 @@ public class ChatMessage
8788
/// <summary>
8889
/// Represents a tool definition that can be used by the model.
8990
/// </summary>
91+
[DebuggerDisplay("Type = {Type}, Name = {Name}")]
9092
public class ToolDefinition
9193
{
9294
public string Type { get; set; } = "function";

0 commit comments

Comments
 (0)