mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
UI cleanups
This commit is contained in:
@@ -14,6 +14,7 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
|
||||
private readonly List<ChatMessage> _pendingMessages = [];
|
||||
private readonly List<Action> _messageChangeSubscribers = [];
|
||||
private readonly List<Action> _responseUpdateSubscribers = [];
|
||||
private readonly List<Action> _runStatusSubscribers = [];
|
||||
private readonly List<AITool> _tools = [];
|
||||
private readonly Dictionary<string, TaskCompletionSource<object>> _pendingResponses = [];
|
||||
|
||||
@@ -29,6 +30,11 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
|
||||
|
||||
public ChatResponseUpdate? CurrentUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the agent is currently processing a turn.
|
||||
/// </summary>
|
||||
public bool IsProcessing { get; private set; }
|
||||
|
||||
public AgentBoundaryContext(AIAgent agent, AgentThread thread)
|
||||
{
|
||||
this._agent = agent;
|
||||
@@ -102,6 +108,12 @@ public sealed partial class AgentBoundaryContext<TState> : 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<TState> : 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<TState> : 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the agent is currently processing a turn.
|
||||
/// </summary>
|
||||
bool IsProcessing { get; }
|
||||
|
||||
// Triggered any time there is a change on a message.
|
||||
MessageSubscription SubscribeToMessageChanges(Action onNewMessage);
|
||||
|
||||
ResponseUpdateSubscription SubscribeToResponseUpdates(Action onChatResponse);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to run status changes (when processing starts or ends).
|
||||
/// </summary>
|
||||
/// <param name="onRunStatusChanged">The callback to invoke when run status changes.</param>
|
||||
/// <returns>A subscription that can be disposed to unsubscribe.</returns>
|
||||
RunStatusSubscription SubscribeToRunStatusChanges(Action onRunStatusChanged);
|
||||
|
||||
CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -326,3 +368,44 @@ public readonly struct ResponseUpdateSubscription : IDisposable, IEquatable<Resp
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct RunStatusSubscription : IDisposable, IEquatable<RunStatusSubscription>
|
||||
{
|
||||
private readonly List<Action> _subscribers;
|
||||
private readonly Action _subscription;
|
||||
|
||||
public RunStatusSubscription(List<Action> 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<List<Action>>.Default.Equals(this._subscribers, other._subscribers) &&
|
||||
EqualityComparer<Action>.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<object?>? _context;
|
||||
private string? _inputText;
|
||||
private RunStatusSubscription? _subscription;
|
||||
|
||||
[CascadingParameter] public AgentBoundaryContext<object?>? 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<ChangeEventArgs>(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, """<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>""");
|
||||
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, """<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>""");
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.AI;
|
||||
|
||||
/// <summary>
|
||||
/// A component that displays a loading indicator (three animated dots) when the agent is processing.
|
||||
/// </summary>
|
||||
public sealed partial class AgentLoadingIndicator : IComponent, IDisposable
|
||||
{
|
||||
private RenderHandle _renderHandle;
|
||||
private AgentBoundaryContext<object?>? _context;
|
||||
private RunStatusSubscription? _subscription;
|
||||
|
||||
[CascadingParameter] public AgentBoundaryContext<object?>? 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, "<span></span><span></span><span></span>");
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._subscription?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -60,11 +60,12 @@ public readonly struct Suggestion : IEquatable<Suggestion>
|
||||
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<object?>? _context;
|
||||
private IReadOnlyList<Suggestion>? _suggestions;
|
||||
private RunStatusSubscription? _subscription;
|
||||
|
||||
[CascadingParameter] public AgentBoundaryContext<object?>? 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+1
@@ -21,6 +21,7 @@
|
||||
<AgentBoundary Agent="@agent">
|
||||
<div class="chat-content">
|
||||
<Messages />
|
||||
<AgentLoadingIndicator />
|
||||
</div>
|
||||
|
||||
<div class="chat-input-container">
|
||||
|
||||
+1
@@ -30,6 +30,7 @@
|
||||
<TextTemplate />
|
||||
</ContentTemplates>
|
||||
</Messages>
|
||||
<AgentLoadingIndicator />
|
||||
</div>
|
||||
|
||||
<div class="chat-input-container">
|
||||
|
||||
+1
@@ -23,6 +23,7 @@
|
||||
<WeatherCallTemplate />
|
||||
</ContentTemplates>
|
||||
</Messages>
|
||||
<AgentLoadingIndicator />
|
||||
</div>
|
||||
|
||||
<div class="chat-input-container">
|
||||
|
||||
+1
@@ -26,6 +26,7 @@
|
||||
<TextTemplate />
|
||||
</ContentTemplates>
|
||||
</Messages>
|
||||
<AgentLoadingIndicator />
|
||||
</div>
|
||||
|
||||
<div class="chat-input-container">
|
||||
|
||||
+1
@@ -54,6 +54,7 @@
|
||||
<TextTemplate />
|
||||
</ContentTemplates>
|
||||
</Messages>
|
||||
<AgentLoadingIndicator />
|
||||
</div>
|
||||
|
||||
<AgentState TState="DocumentState"
|
||||
|
||||
+8
-3
@@ -216,11 +216,11 @@
|
||||
}
|
||||
|
||||
/* Agent Suggestions styling overrides */
|
||||
::deep .suggestions-container {
|
||||
::deep .agent-suggestions {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
::deep .suggestion-chip {
|
||||
::deep .agent-suggestions .suggestion-button {
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 8px 14px;
|
||||
@@ -231,7 +231,12 @@
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
::deep .suggestion-chip:hover {
|
||||
::deep .agent-suggestions .suggestion-button:hover:not(:disabled) {
|
||||
background: #e5e7eb;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
::deep .agent-suggestions .suggestion-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
+1
@@ -116,6 +116,7 @@
|
||||
<TextTemplate />
|
||||
</ContentTemplates>
|
||||
</Messages>
|
||||
<AgentLoadingIndicator />
|
||||
</div>
|
||||
<div class="chat-input-container">
|
||||
<AgentInput Placeholder="Ask about your recipe..." />
|
||||
|
||||
+1
@@ -30,6 +30,7 @@
|
||||
<TextTemplate />
|
||||
</ContentTemplates>
|
||||
</Messages>
|
||||
<AgentLoadingIndicator />
|
||||
</div>
|
||||
|
||||
<div class="chat-input-container">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user