From 0c93762cb6b4203fcc927d73d8a527570fc78109 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 11 Dec 2025 13:13:53 +0100 Subject: [PATCH] UI cleanups --- .../Components/AI/AgentBoundaryContext.cs | 83 +++++ .../Components/AI/AgentInput.cs | 44 ++- .../Components/AI/AgentLoadingIndicator.cs | 72 ++++ .../Components/AI/AgentSuggestions.cs | 35 +- .../Components/AI/Messages/MessageList.cs | 1 + .../Components/AI/components.ai.css | 309 ++++++++++++++++++ .../Demos/AgenticChat/AgenticChatDemo.razor | 1 + .../AgenticGenerativeUIDemo.razor | 1 + .../BackendToolRenderingDemo.razor | 1 + .../HumanInTheLoop/HumanInTheLoopDemo.razor | 1 + .../PredictiveStateUpdatesDemo.razor | 1 + .../PredictiveStateUpdatesDemo.razor.css | 11 +- .../Demos/SharedState/SharedStateDemo.razor | 1 + .../ToolBasedGenerativeUIDemo.razor | 1 + .../AGUIDojo/AGUIDojoClient/wwwroot/app.css | 119 +++++++ 15 files changed, 666 insertions(+), 15 deletions(-) create mode 100644 dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentLoadingIndicator.cs create mode 100644 dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/components.ai.css diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentBoundaryContext.cs b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentBoundaryContext.cs index a28f042958..fd08034731 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentBoundaryContext.cs +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentBoundaryContext.cs @@ -14,6 +14,7 @@ public sealed partial class AgentBoundaryContext : IAgentBoundaryContext private readonly List _pendingMessages = []; private readonly List _messageChangeSubscribers = []; private readonly List _responseUpdateSubscribers = []; + private readonly List _runStatusSubscribers = []; private readonly List _tools = []; private readonly Dictionary> _pendingResponses = []; @@ -29,6 +30,11 @@ public sealed partial class AgentBoundaryContext : IAgentBoundaryContext public ChatResponseUpdate? CurrentUpdate { get; private set; } + /// + /// Gets whether the agent is currently processing a turn. + /// + public bool IsProcessing { get; private set; } + public AgentBoundaryContext(AIAgent agent, AgentThread thread) { this._agent = agent; @@ -102,6 +108,12 @@ public sealed partial class AgentBoundaryContext : IAgentBoundaryContext this._pendingMessages.Clear(); this._pendingMessages.AddRange(userMessages); + // Mark as processing + this.IsProcessing = true; + + // Notify subscribers that run status changed (processing started) + this.TriggerRunStatusChanged(); + // User messages added, notify subscribers. this.TriggerMessageChanges(); @@ -159,6 +171,10 @@ public sealed partial class AgentBoundaryContext : IAgentBoundaryContext this._pendingMessages.Clear(); this.CurrentMessage = null; this.CurrentUpdate = null; + this.IsProcessing = false; + + // Notify subscribers that run status changed (processing ended) + this.TriggerRunStatusChanged(); // Notify subscribers this.TriggerChatResponseUpdate(); @@ -192,6 +208,20 @@ public sealed partial class AgentBoundaryContext : IAgentBoundaryContext { return new ResponseUpdateSubscription(this._responseUpdateSubscribers, onChatResponse); } + + public RunStatusSubscription SubscribeToRunStatusChanges(Action onRunStatusChanged) + { + return new RunStatusSubscription(this._runStatusSubscribers, onRunStatusChanged); + } + + private void TriggerRunStatusChanged() + { + // Iterate backwards to avoid issues if subscribers are removed during iteration + for (var i = this._runStatusSubscribers.Count - 1; i >= 0; i--) + { + this._runStatusSubscribers[i](); + } + } } public interface IAgentBoundaryContext @@ -210,11 +240,23 @@ public interface IAgentBoundaryContext ChatResponseUpdate? CurrentUpdate { get; } + /// + /// Gets whether the agent is currently processing a turn. + /// + bool IsProcessing { get; } + // Triggered any time there is a change on a message. MessageSubscription SubscribeToMessageChanges(Action onNewMessage); ResponseUpdateSubscription SubscribeToResponseUpdates(Action onChatResponse); + /// + /// Subscribes to run status changes (when processing starts or ends). + /// + /// The callback to invoke when run status changes. + /// A subscription that can be disposed to unsubscribe. + RunStatusSubscription SubscribeToRunStatusChanges(Action onRunStatusChanged); + CancellationToken CancellationToken { get; } /// @@ -326,3 +368,44 @@ public readonly struct ResponseUpdateSubscription : IDisposable, IEquatable +{ + private readonly List _subscribers; + private readonly Action _subscription; + + public RunStatusSubscription(List subscribers, Action subscription) + { + this._subscribers = subscribers; + this._subscription = subscription; + this._subscribers.Add(this._subscription); + } + + public void Dispose() => this._subscribers.Remove(this._subscription); + + public override bool Equals(object? obj) + { + return obj is RunStatusSubscription subscription && this.Equals(subscription); + } + + public bool Equals(RunStatusSubscription other) + { + return EqualityComparer>.Default.Equals(this._subscribers, other._subscribers) && + EqualityComparer.Default.Equals(this._subscription, other._subscription); + } + + public override int GetHashCode() + { + return HashCode.Combine(this._subscribers, this._subscription); + } + + public static bool operator ==(RunStatusSubscription left, RunStatusSubscription right) + { + return left.Equals(right); + } + + public static bool operator !=(RunStatusSubscription left, RunStatusSubscription right) + { + return !(left == right); + } +} diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentInput.cs b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentInput.cs index 3a8c6c944f..7212cb1ba1 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentInput.cs +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentInput.cs @@ -4,11 +4,12 @@ using Microsoft.Extensions.AI; namespace Microsoft.AspNetCore.Components.AI; -public partial class AgentInput : IComponent +public sealed partial class AgentInput : IComponent, IDisposable { private RenderHandle _renderHandle; private AgentBoundaryContext? _context; private string? _inputText; + private RunStatusSubscription? _subscription; [CascadingParameter] public AgentBoundaryContext? AgentContext { get; set; } @@ -22,13 +23,34 @@ public partial class AgentInput : IComponent public Task SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); - this._context = this.AgentContext; + + // Unsubscribe from previous context if it changed + if (this._context != this.AgentContext) + { + this._subscription?.Dispose(); + this._context = this.AgentContext; + + // Subscribe to run status changes + if (this._context != null) + { + this._subscription = this._context.SubscribeToRunStatusChanges(this.OnRunStatusChanged); + } + } + this.Render(); return Task.CompletedTask; } + private void OnRunStatusChanged() + { + this.Render(); + } + private void Render() { + var isProcessing = this._context?.IsProcessing ?? false; + var isDisabled = string.IsNullOrWhiteSpace(this._inputText) || isProcessing; + this._renderHandle.Render(builder => { builder.OpenElement(0, "div"); @@ -39,13 +61,14 @@ public partial class AgentInput : IComponent builder.AddAttribute(4, "oninput", EventCallback.Factory.Create(this, e => { this._inputText = e.Value?.ToString(); this.Render(); })); builder.AddAttribute(5, "placeholder", this.Placeholder); builder.AddAttribute(6, "rows", "1"); + builder.AddAttribute(7, "disabled", isProcessing); builder.CloseElement(); - builder.OpenElement(7, "button"); - builder.AddAttribute(8, "class", "send-button"); - builder.AddAttribute(9, "onclick", EventCallback.Factory.Create(this, this.SendAsync)); - builder.AddAttribute(10, "disabled", string.IsNullOrWhiteSpace(this._inputText)); - builder.AddMarkupContent(11, """"""); + builder.OpenElement(8, "button"); + builder.AddAttribute(9, "class", "send-button"); + builder.AddAttribute(10, "onclick", EventCallback.Factory.Create(this, this.SendAsync)); + builder.AddAttribute(11, "disabled", isDisabled); + builder.AddMarkupContent(12, """"""); builder.CloseElement(); // close button builder.CloseElement(); // close div @@ -54,7 +77,7 @@ public partial class AgentInput : IComponent private async Task SendAsync() { - if (!string.IsNullOrWhiteSpace(this._inputText) && this._context != null) + if (!string.IsNullOrWhiteSpace(this._inputText) && this._context is { IsProcessing: false }) { var text = this._inputText; this._inputText = ""; // Clear input immediately @@ -63,4 +86,9 @@ public partial class AgentInput : IComponent await this._context.SendAsync(new ChatMessage(ChatRole.User, text)); } } + + public void Dispose() + { + this._subscription?.Dispose(); + } } diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentLoadingIndicator.cs b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentLoadingIndicator.cs new file mode 100644 index 0000000000..a48f4eb591 --- /dev/null +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentLoadingIndicator.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.AspNetCore.Components.AI; + +/// +/// A component that displays a loading indicator (three animated dots) when the agent is processing. +/// +public sealed partial class AgentLoadingIndicator : IComponent, IDisposable +{ + private RenderHandle _renderHandle; + private AgentBoundaryContext? _context; + private RunStatusSubscription? _subscription; + + [CascadingParameter] public AgentBoundaryContext? AgentContext { get; set; } + + public void Attach(RenderHandle renderHandle) + { + this._renderHandle = renderHandle; + } + + public Task SetParametersAsync(ParameterView parameters) + { + parameters.SetParameterProperties(this); + + // Unsubscribe from previous context if it changed + if (this._context != this.AgentContext) + { + this._subscription?.Dispose(); + this._context = this.AgentContext; + + // Subscribe to run status changes + if (this._context != null) + { + this._subscription = this._context.SubscribeToRunStatusChanges(this.OnRunStatusChanged); + } + } + + this.Render(); + return Task.CompletedTask; + } + + private void OnRunStatusChanged() + { + this.Render(); + } + + private void Render() + { + var isProcessing = this._context?.IsProcessing ?? false; + + this._renderHandle.Render(builder => + { + if (!isProcessing) + { + return; + } + + builder.OpenElement(0, "div"); + builder.AddAttribute(1, "class", "agent-loading-indicator"); + builder.OpenElement(2, "div"); + builder.AddAttribute(3, "class", "agent-loading-dots"); + builder.AddMarkupContent(4, ""); + builder.CloseElement(); + builder.CloseElement(); + }); + } + + public void Dispose() + { + this._subscription?.Dispose(); + } +} diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentSuggestions.cs b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentSuggestions.cs index 2214f52de0..5223449107 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentSuggestions.cs +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/AgentSuggestions.cs @@ -60,11 +60,12 @@ public readonly struct Suggestion : IEquatable public static bool operator !=(Suggestion left, Suggestion right) => !left.Equals(right); } -public partial class AgentSuggestions : IComponent +public sealed partial class AgentSuggestions : IComponent, IDisposable { private RenderHandle _renderHandle; private AgentBoundaryContext? _context; private IReadOnlyList? _suggestions; + private RunStatusSubscription? _subscription; [CascadingParameter] public AgentBoundaryContext? AgentContext { get; set; } @@ -78,14 +79,34 @@ public partial class AgentSuggestions : IComponent public Task SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); - this._context = this.AgentContext; + + // Unsubscribe from previous context if it changed + if (this._context != this.AgentContext) + { + this._subscription?.Dispose(); + this._context = this.AgentContext; + + // Subscribe to run status changes + if (this._context != null) + { + this._subscription = this._context.SubscribeToRunStatusChanges(this.OnRunStatusChanged); + } + } + this._suggestions = this.Suggestions; this.Render(); return Task.CompletedTask; } + private void OnRunStatusChanged() + { + this.Render(); + } + private void Render() { + var isProcessing = this._context?.IsProcessing ?? false; + this._renderHandle.Render(builder => { if (this._suggestions is null || this._suggestions.Count == 0) @@ -103,7 +124,8 @@ public partial class AgentSuggestions : IComponent builder.SetKey(suggestion.Text); builder.AddAttribute(3, "class", "suggestion-button"); builder.AddAttribute(4, "onclick", EventCallback.Factory.Create(this, () => this.SelectSuggestionAsync(suggestion))); - builder.AddContent(5, suggestion.Text); + builder.AddAttribute(5, "disabled", isProcessing); + builder.AddContent(6, suggestion.Text); builder.CloseElement(); } @@ -113,9 +135,14 @@ public partial class AgentSuggestions : IComponent private async Task SelectSuggestionAsync(Suggestion suggestion) { - if (this._context != null) + if (this._context is { IsProcessing: false }) { await this._context.SendAsync(suggestion.Message); } } + + public void Dispose() + { + this._subscription?.Dispose(); + } } diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs index 86899574f4..a83d7403ab 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Linq; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.AI; diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/components.ai.css b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/components.ai.css new file mode 100644 index 0000000000..d46f285661 --- /dev/null +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/components.ai.css @@ -0,0 +1,309 @@ +/* Copyright (c) Microsoft. All rights reserved. */ +/* ========================================================================== + AG-UI Components - Shared Styles + These styles provide consistent appearance for AI components across demos. + ========================================================================== */ + +/* ========================================================================== + Agent Input Component + ========================================================================== */ + +.agent-input { + display: flex; + align-items: flex-end; + gap: 12px; + width: 100%; +} + +.agent-input textarea { + flex: 1; + padding: 12px 16px; + border: 1px solid #d1d5db; + border-radius: 8px; + font-size: 14px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + resize: none; + min-height: 44px; + max-height: 200px; + line-height: 1.5; + outline: none; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.agent-input textarea:focus { + border-color: #2563eb; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.agent-input textarea::placeholder { + color: #9ca3af; +} + +.agent-input textarea:disabled { + background-color: #f9fafb; + color: #9ca3af; + cursor: not-allowed; +} + +.agent-input .send-button { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border: none; + border-radius: 8px; + background-color: #2563eb; + color: white; + cursor: pointer; + transition: background-color 0.2s, opacity 0.2s; + flex-shrink: 0; +} + +.agent-input .send-button:hover:not(:disabled) { + background-color: #1d4ed8; +} + +.agent-input .send-button:disabled { + background-color: #d1d5db; + color: #9ca3af; + cursor: not-allowed; +} + +.agent-input .send-button svg { + display: block; +} + +/* ========================================================================== + Agent Suggestions Component + ========================================================================== */ + +.agent-suggestions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + justify-content: flex-start; + margin-bottom: 0.75rem; +} + +.suggestion-button { + padding: 0.5rem 1rem; + background: white; + border: 1px solid #d1d1d1; + border-radius: 1rem; + cursor: pointer; + font-size: 0.875rem; + color: #424242; + transition: all 0.2s; +} + +.suggestion-button:hover:not(:disabled) { + background: #f0f0f0; + border-color: #0078d4; + color: #0078d4; +} + +.suggestion-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* ========================================================================== + Loading Indicator - Triple Dot Animation + ========================================================================== */ + +.agent-loading-indicator { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + color: #6b7280; + font-size: 14px; +} + +.agent-loading-dots { + display: flex; + gap: 4px; + align-items: center; +} + +.agent-loading-dots span { + width: 6px; + height: 6px; + background-color: #2563eb; + border-radius: 50%; + animation: agent-dot-bounce 1.4s ease-in-out infinite both; +} + +.agent-loading-dots span:nth-child(1) { + animation-delay: -0.32s; +} + +.agent-loading-dots span:nth-child(2) { + animation-delay: -0.16s; +} + +.agent-loading-dots span:nth-child(3) { + animation-delay: 0s; +} + +@keyframes agent-dot-bounce { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.5; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + +/* Inline loading indicator for use within messages */ +.agent-loading-inline { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + background-color: #f3f4f6; + border-radius: 12px; +} + +.agent-loading-inline span { + width: 5px; + height: 5px; + background-color: #6b7280; + border-radius: 50%; + animation: agent-dot-bounce 1.4s ease-in-out infinite both; +} + +.agent-loading-inline span:nth-child(1) { + animation-delay: -0.32s; +} + +.agent-loading-inline span:nth-child(2) { + animation-delay: -0.16s; +} + +.agent-loading-inline span:nth-child(3) { + animation-delay: 0s; +} + +/* ========================================================================== + Processing State Overlay + ========================================================================== */ + +.agent-input-container { + position: relative; +} + +.agent-input-container.processing .agent-input textarea { + background-color: #f9fafb; +} + +/* ========================================================================== + Message Styles + ========================================================================== */ + +.message-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.message { + display: flex; + flex-direction: column; + gap: 4px; +} + +.message-user { + align-items: flex-end; +} + +.message-assistant { + align-items: flex-start; +} + +.message-content { + max-width: 80%; + padding: 12px 16px; + border-radius: 12px; + font-size: 14px; + line-height: 1.5; +} + +.message-user .message-content { + background-color: #2563eb; + color: white; + border-bottom-right-radius: 4px; +} + +.message-assistant .message-content { + background-color: #f3f4f6; + color: #1f2937; + border-bottom-left-radius: 4px; +} + +/* ========================================================================== + Common Chat Layout Styles + ========================================================================== */ + +.chat-layout { + display: flex; + flex-direction: column; + height: 100%; + max-width: 1200px; + margin: 0 auto; +} + +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem 2rem; + border-bottom: 1px solid #e5e5e5; +} + +.chat-title { + font-size: 1.5rem; + font-weight: 600; + color: #1a1a1a; +} + +.new-chat-button { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: white; + border: 1px solid #d1d1d1; + border-radius: 4px; + cursor: pointer; + font-size: 0.875rem; + color: #424242; + transition: all 0.2s; +} + +.new-chat-button:hover { + background: #f5f5f5; + border-color: #b3b3b3; +} + +.button-icon { + font-size: 1.2rem; + line-height: 1; +} + +.chat-content { + flex: 1; + overflow-y: auto; + padding: 2rem; + display: flex; + flex-direction: column; +} + +.chat-input-container { + padding: 1.5rem 2rem 2rem; + border-top: 1px solid #e5e5e5; + background: white; +} diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor index da7ba1f975..bd311a4255 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor @@ -21,6 +21,7 @@
+
diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticGenerativeUI/AgenticGenerativeUIDemo.razor b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticGenerativeUI/AgenticGenerativeUIDemo.razor index 79d7926463..24fec8e84b 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticGenerativeUI/AgenticGenerativeUIDemo.razor +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticGenerativeUI/AgenticGenerativeUIDemo.razor @@ -30,6 +30,7 @@ +
diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/BackendToolRendering/BackendToolRenderingDemo.razor b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/BackendToolRendering/BackendToolRenderingDemo.razor index 65b62fb30d..df21874677 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/BackendToolRendering/BackendToolRenderingDemo.razor +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/BackendToolRendering/BackendToolRenderingDemo.razor @@ -23,6 +23,7 @@ +
diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/HumanInTheLoop/HumanInTheLoopDemo.razor b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/HumanInTheLoop/HumanInTheLoopDemo.razor index f38f35d26f..d7023b0be2 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/HumanInTheLoop/HumanInTheLoopDemo.razor +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/HumanInTheLoop/HumanInTheLoopDemo.razor @@ -26,6 +26,7 @@ +
diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/PredictiveStateUpdates/PredictiveStateUpdatesDemo.razor b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/PredictiveStateUpdates/PredictiveStateUpdatesDemo.razor index 2c2342694b..e1372ec673 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/PredictiveStateUpdates/PredictiveStateUpdatesDemo.razor +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/PredictiveStateUpdates/PredictiveStateUpdatesDemo.razor @@ -54,6 +54,7 @@ +
+
diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/ToolBasedGenerativeUI/ToolBasedGenerativeUIDemo.razor b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/ToolBasedGenerativeUI/ToolBasedGenerativeUIDemo.razor index 66df8b2db8..e0fe672261 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/ToolBasedGenerativeUI/ToolBasedGenerativeUIDemo.razor +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/ToolBasedGenerativeUI/ToolBasedGenerativeUIDemo.razor @@ -30,6 +30,7 @@ +
diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css b/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css index 8cb3035a13..d691fdbf82 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css @@ -148,3 +148,122 @@ h1:focus { .agent-input .send-button svg { display: block; } + +.agent-input textarea:disabled { + background-color: #f3f4f6; + color: #9ca3af; + cursor: not-allowed; +} + +/* Loading dots animation */ +.agent-loading-dots { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + gap: 4px; +} + +.agent-loading-dots span { + width: 8px; + height: 8px; + background-color: #2563eb; + border-radius: 50%; + animation: agent-dot-bounce 1.4s ease-in-out infinite both; +} + +.agent-loading-dots span:nth-child(1) { + animation-delay: -0.32s; +} + +.agent-loading-dots span:nth-child(2) { + animation-delay: -0.16s; +} + +.agent-loading-dots span:nth-child(3) { + animation-delay: 0s; +} + +@keyframes agent-dot-bounce { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.5; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + +/* Loading indicator in message list */ +.agent-loading-indicator { + display: flex; + align-items: flex-start; + padding: 12px 16px; +} + +.agent-loading-indicator .agent-loading-dots { + background-color: #f3f4f6; + border-radius: 16px; + padding: 12px 16px; + width: auto; + height: auto; +} + +.agent-loading-indicator .agent-loading-dots span { + width: 6px; + height: 6px; + background-color: #6b7280; +} + +/* AgentSuggestions component styles */ +.agent-suggestions { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 8px 16px; +} + +.agent-suggestions .suggestion-button { + padding: 8px 16px; + border: 1px solid #d1d5db; + border-radius: 20px; + background-color: #f9fafb; + color: #374151; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; +} + +.agent-suggestions .suggestion-button:hover:not(:disabled) { + border-color: #2563eb; + background-color: #eff6ff; + color: #2563eb; +} + +.agent-suggestions .suggestion-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Chat layout common styles */ +.chat-layout { + display: flex; + flex-direction: column; + height: 100%; + max-width: 800px; + margin: 0 auto; + background-color: #ffffff; +} + +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.chat-input-area { + border-top: 1px solid #e5e7eb; + background-color: #fff; +}