Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/Aspire.Dashboard/Components/Dialogs/AIAgentsDialog.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@using Aspire.Dashboard.Resources

<FluentDialogHeader ShowDismiss="true">
<div class="dialog-title-grid">
<FluentIcon Value="@(new Icons.Regular.Size24.BotSparkle())" Style="grid-area: dialog-icon; align-self: center;" />
<FluentLabel Typo="Typography.PaneHeader" Class="col-long-content" Style="grid-area: dialog-title;">
@Loc[nameof(Dialogs.AIAgentsDialogTitle)]
Comment thread
JamesNK marked this conversation as resolved.
Resolved
</FluentLabel>
</div>
</FluentDialogHeader>

<FluentDialogBody Style="overflow-x: hidden">
<div class="markdown-content">
<MarkdownRenderer Markdown="@Description" MarkdownProcessor="@GetMarkdownProcessor()" SuppressParagraphOnNewLines="false" />
</div>
</FluentDialogBody>
68 changes: 68 additions & 0 deletions src/Aspire.Dashboard/Components/Dialogs/AIAgentsDialog.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using Aspire.Dashboard.Configuration;
using Aspire.Dashboard.Model.Markdown;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Microsoft.FluentUI.AspNetCore.Components;

using DialogsLoc = Aspire.Dashboard.Resources.Dialogs;

namespace Aspire.Dashboard.Components.Dialogs;

public partial class AIAgentsDialog : IDialogContentComponent
{
private MarkdownProcessor? _markdownProcessor;

[Inject]
public required IStringLocalizer<DialogsLoc> Loc { get; init; }

[Inject]
public required IStringLocalizer<Resources.ControlsStrings> ControlsStringsLoc { get; init; }

[Inject]
public required IDashboardClient DashboardClient { get; init; }

[Inject]
public required IOptionsMonitor<DashboardOptions> Options { get; init; }

private const string AppHostLearnMoreUrl = "https://aspire.dev/get-started/ai-coding-agents/";
private const string StandaloneLearnMoreUrl = "https://aspire.dev/dashboard/ai-coding-agents/";

private string Description => DashboardClient.IsEnabled
? string.Format(CultureInfo.CurrentCulture, Loc[nameof(DialogsLoc.AIAgentsDialogAppHostDescription)], AppHostLearnMoreUrl)
: string.Format(CultureInfo.CurrentCulture, Loc[nameof(DialogsLoc.AIAgentsDialogStandaloneDescription)], GetDashboardUrl(), StandaloneLearnMoreUrl);

Comment thread
JamesNK marked this conversation as resolved.
Resolved
private string GetDashboardUrl()
{
var options = Options.CurrentValue;
var endpointAddresses = options.Frontend.GetEndpointAddresses();
var baseUrl = options.Frontend.PublicUrl
?? (endpointAddresses.Count > 0 ? endpointAddresses[0].ToString() : null);

Comment thread
JamesNK marked this conversation as resolved.
Resolved
if (baseUrl is null)
{
return "http://localhost:18888";
}

// Trim trailing slash for consistent URL building.
baseUrl = baseUrl.TrimEnd('/');

if (options.Api.AuthMode is ApiAuthMode.ApiKey &&
options.Frontend.AuthMode is FrontendAuthMode.BrowserToken &&
options.Frontend.BrowserToken is { } token)
{
return $"{baseUrl}/login?t={token}";
}

return baseUrl;
}
Comment thread
JamesNK marked this conversation as resolved.
Resolved

private MarkdownProcessor GetMarkdownProcessor()
{
return _markdownProcessor ??= new MarkdownProcessor(ControlsStringsLoc, safeUrlSchemes: MarkdownHelpers.SafeUrlSchemes, extensions: []);
}
}
10 changes: 10 additions & 0 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
Title="@Loc[nameof(Layout.MainLayoutAspireDashboardHelpLink)]" aria-label="@Loc[nameof(Layout.MainLayoutAspireDashboardHelpLink)]">
<FluentIcon Value="@(new Icons.Regular.Size24.QuestionCircle())" Color="Color.Neutral"/>
</FluentButton>
@if (Options.CurrentValue.UI.DisableAgentHelp != true)
{
<FluentButton Class="header-button"
Appearance="Appearance.Stealth" OnClick="@LaunchAIAgentsAsync"
Title="@Loc[nameof(Layout.MainLayoutLaunchAIAgents)]" aria-label="@Loc[nameof(Layout.MainLayoutLaunchAIAgents)]">
<FluentIcon Value="@(new Icons.Regular.Size24.BotSparkle())" Color="Color.Neutral"/>
</FluentButton>
}
@if (AIContextProvider.Enabled)
{
<FluentButton Class="@("header-button" + ((AIContextProvider.AssistantChatViewModel is {} vm && AIContextProvider.ShowAssistantSidebarDialog) ? " header-button-selected" : ""))"
Expand Down Expand Up @@ -79,6 +87,8 @@
IsAIEnabled="@AIContextProvider.Enabled"
CloseNavMenu="@CloseMobileNavMenu"
LaunchHelpAsync="@LaunchHelpAsync"
LaunchAIAgentsAsync="@LaunchAIAgentsAsync"
IsAgentHelpEnabled="@(Options.CurrentValue.UI.DisableAgentHelp != true)"
LaunchAIAssistantAsync="@LaunchAssistantAsync"
LaunchNotificationsAsync="@LaunchNotificationsAsync"
LaunchSettingsAsync="@LaunchSettingsAsync" />
Expand Down
31 changes: 31 additions & 0 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public partial class MainLayout : IGlobalKeydownListener, IAsyncDisposable
private const string SettingsDialogId = "SettingsDialog";
private const string HelpDialogId = "HelpDialog";
private const string NotificationsDialogId = "NotificationsDialog";
private const string AIAgentsDialogId = "AIAgentsDialog";

[Inject]
public required ThemeManager ThemeManager { get; init; }
Expand Down Expand Up @@ -247,6 +248,36 @@ private void HandleDialogClose(DialogInstance dialogResult)
_openPageDialog = null;
}

public async Task LaunchAIAgentsAsync()
{
DialogParameters parameters = new()
{
Title = Loc[nameof(Resources.Layout.MainLayoutLaunchAIAgents)],
PrimaryAction = Loc[nameof(Resources.Layout.MainLayoutSettingsDialogClose)],
PrimaryActionEnabled = true,
SecondaryAction = null,
TrapFocus = true,
Modal = true,
Alignment = HorizontalAlignment.Center,
Width = "700px",
Height = "auto",
Id = AIAgentsDialogId,
OnDialogClosing = EventCallback.Factory.Create<DialogInstance>(this, HandleDialogClose)
};

if (_openPageDialog is not null)
{
if (Equals(_openPageDialog.Id, AIAgentsDialogId) && !_openPageDialog.Result.IsCompleted)
{
return;
}

await _openPageDialog.CloseAsync();
}

_openPageDialog = await DialogService.ShowDialogAsync<AIAgentsDialog>(parameters).ConfigureAwait(true);
}

public async Task LaunchSettingsAsync()
{
var parameters = new DialogParameters
Expand Down
6 changes: 6 additions & 0 deletions src/Aspire.Dashboard/Components/Layout/MobileNavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
[Parameter, EditorRequired]
public required Func<Task> LaunchHelpAsync { get; set; }

[Parameter, EditorRequired]
public required Func<Task> LaunchAIAgentsAsync { get; set; }

[Parameter, EditorRequired]
public required bool IsAgentHelpEnabled { get; set; }

[Parameter, EditorRequired]
public required Func<Task> LaunchAIAssistantAsync { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ private IEnumerable<MobileNavMenuEntry> GetMobileNavMenuEntries()
new Icons.Regular.Size24.QuestionCircle()
);

if (IsAgentHelpEnabled)
{
yield return new MobileNavMenuEntry(
Loc[nameof(Resources.Layout.MainLayoutLaunchAIAgents)],
LaunchAIAgentsAsync,
new Icons.Regular.Size24.BotSparkle()
);
}

if (IsAIEnabled)
{
yield return new MobileNavMenuEntry(
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Dashboard/Configuration/DashboardOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ public sealed class UIOptions
{
public bool? DisableResourceGraph { get; set; }
public bool? DisableImport { get; set; }
public bool? DisableAgentHelp { get; set; }
}

// Don't set values after validating/parsing options.
Expand Down
27 changes: 27 additions & 0 deletions src/Aspire.Dashboard/Resources/Dialogs.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions src/Aspire.Dashboard/Resources/Dialogs.resx
Original file line number Diff line number Diff line change
Expand Up @@ -504,4 +504,44 @@
<data name="NotificationEntryDismiss" xml:space="preserve">
<value>Dismiss notification</value>
</data>
<data name="AIAgentsDialogTitle" xml:space="preserve">
<value>AI agents</value>
</data>
<data name="AIAgentsDialogStandaloneDescription" xml:space="preserve">
<value>Build, debug, and profile apps using AI agents and Aspire 🚀

The structured logs and distributed traces in the dashboard are available to AI agents via the Aspire CLI and MCP server. This gives agents deep observability into your app, to help them diagnose issues and verify fixes.

Use Aspire CLI commands to get telemetry data in your terminal:

```bash
aspire otel logs --dashboard-url {0}
```

Or start the Aspire MCP server:

```bash
aspire agent mcp --dashboard-url {0}
```

For more information about using AI agents with the dashboard, such as setting up skills, see [Standalone dashboard and AI coding agents]({1}).</value>
<comment>{0} is the dashboard URL, {1} is the learn more URL</comment>
</data>
<data name="AIAgentsDialogAppHostDescription" xml:space="preserve">
<value>Build, debug, and profile apps using AI agents and Aspire 🚀

The structured logs, distributed traces, and resource status in this dashboard are available to AI agents via the Aspire CLI and MCP server. This gives agents deep observability into your app, to help them diagnose issues and verify fixes.

Get started by running this command in your project:

```bash
aspire agent init
```

This configures skill files for your AI assistant. The skill files tell the agent how to use Aspire CLI commands such as `aspire otel logs`, `aspire otel traces`, and `aspire agent mcp`.

Once connected, agents can start your app, check resource health, fetch logs and traces, and more.

For more information about using AI agents with Aspire, such as setting up skills, see [AI coding agents]({0}).</value>
</data>
</root>
9 changes: 9 additions & 0 deletions src/Aspire.Dashboard/Resources/Layout.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Aspire.Dashboard/Resources/Layout.resx
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,7 @@
<data name="MessageUnsecuredEndpointApiBody" xml:space="preserve">
<value>Untrusted apps can access telemetry data via the API.</value>
</data>
<data name="MainLayoutLaunchAIAgents" xml:space="preserve">
<value>AI Agents</value>
</data>
</root>
75 changes: 75 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Dialogs.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading