using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; namespace AxCopilot.Views; public partial class ChatWindow { private void ShowAgentLiveCard(string runTab) { if (MessageList == null) return; if (!string.Equals(runTab, _activeTab, StringComparison.OrdinalIgnoreCase)) return; RemoveAgentLiveCard(animated: false); _agentLiveStartTime = DateTime.UtcNow; _agentLiveSubItemTexts.Clear(); _agentLiveCurrentCategory = null; var msgMaxWidth = GetMessageMaxWidth(); var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var container = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center, Width = msgMaxWidth, MaxWidth = msgMaxWidth, Margin = new Thickness(0, 4, 0, 6), Opacity = IsLightweightLiveProgressMode(runTab) ? 1 : 0, RenderTransform = IsLightweightLiveProgressMode(runTab) ? Transform.Identity : new TranslateTransform(0, 8), }; if (!IsLightweightLiveProgressMode(runTab)) { container.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(260))); ((TranslateTransform)container.RenderTransform).BeginAnimation( TranslateTransform.YProperty, new DoubleAnimation(8, 0, TimeSpan.FromMilliseconds(280)) { EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } }); } var headerGrid = new Grid { Margin = new Thickness(2, 0, 0, 3) }; headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); var liveIcon = CreateMiniLauncherIcon(pixelSize: 4.0); if (!IsLightweightLiveProgressMode(runTab)) { liveIcon.BeginAnimation( UIElement.OpacityProperty, new DoubleAnimation(1.0, 0.35, TimeSpan.FromMilliseconds(750)) { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever, EasingFunction = new SineEase() }); } Grid.SetColumn(liveIcon, 0); headerGrid.Children.Add(liveIcon); var (agentName, _, _) = GetAgentIdentity(); var nameTb = new TextBlock { Text = agentName, FontSize = 11, FontWeight = FontWeights.SemiBold, Foreground = secondaryText, Margin = new Thickness(6, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, }; Grid.SetColumn(nameTb, 1); headerGrid.Children.Add(nameTb); _agentLiveElapsedText = new TextBlock { Text = "", FontSize = 10, Foreground = secondaryText, Opacity = 0.50, HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Center, }; Grid.SetColumn(_agentLiveElapsedText, 2); headerGrid.Children.Add(_agentLiveElapsedText); container.Children.Add(headerGrid); var card = new Border { BorderBrush = borderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(12), Padding = new Thickness(13, 10, 13, 10), }; card.SetResourceReference(Border.BackgroundProperty, "ItemBackground"); var cardStack = new StackPanel(); _agentLiveStatusText = new TextBlock { Text = "준비 중...", FontSize = 12, FontFamily = new FontFamily("Segoe UI, Malgun Gothic"), Foreground = secondaryText, TextWrapping = TextWrapping.Wrap, }; cardStack.Children.Add(_agentLiveStatusText); _agentLiveSubItems = new StackPanel { Margin = new Thickness(0, 6, 0, 0) }; cardStack.Children.Add(_agentLiveSubItems); card.Child = cardStack; container.Children.Add(card); _agentLiveContainer = container; AddTranscriptElement(container); ForceScrollToEnd(); _agentLiveElapsedTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _agentLiveElapsedTimer.Tick += (_, _) => { if (_agentLiveElapsedText == null) return; var sec = (int)(DateTime.UtcNow - _agentLiveStartTime).TotalSeconds; _agentLiveElapsedText.Text = sec > 0 ? $"{sec}초 경과" : ""; }; _agentLiveElapsedTimer.Start(); } private void UpdateAgentLiveCard(string message, string? subItem = null, string? category = null, bool clearSubItems = false) { if (_agentLiveContainer == null || _agentLiveStatusText == null) return; _agentLiveStatusText.Text = message; if (clearSubItems || (category != null && category != _agentLiveCurrentCategory)) { _agentLiveSubItemTexts.Clear(); _agentLiveSubItems?.Children.Clear(); if (category != null) _agentLiveCurrentCategory = category; } if (string.IsNullOrEmpty(subItem) || _agentLiveSubItemTexts.Contains(subItem)) return; _agentLiveSubItemTexts.Add(subItem); const int maxLiveSubItems = 8; if (_agentLiveSubItemTexts.Count > maxLiveSubItems) { _agentLiveSubItemTexts.RemoveAt(0); if (_agentLiveSubItems?.Children.Count > 0) _agentLiveSubItems.Children.RemoveAt(0); } var secondary = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var tb = new TextBlock { Text = $"› {subItem}", FontSize = 10.5, FontFamily = new FontFamily("Segoe UI, Malgun Gothic"), Foreground = secondary, Opacity = 0.62, TextTrimming = TextTrimming.CharacterEllipsis, MaxWidth = 520, Margin = new Thickness(0, 1, 0, 0), }; _agentLiveSubItems?.Children.Add(tb); ForceScrollToEnd(); } private void RemoveAgentLiveCard(bool animated = true) { _agentLiveElapsedTimer?.Stop(); _agentLiveElapsedTimer = null; if (_agentLiveContainer == null) return; var toRemove = _agentLiveContainer; _agentLiveContainer = null; _agentLiveStatusText = null; _agentLiveSubItems = null; _agentLiveElapsedText = null; _agentLiveSubItemTexts.Clear(); _agentLiveCurrentCategory = null; if (animated && ContainsTranscriptElement(toRemove) && !IsLightweightLiveProgressMode()) { var anim = new DoubleAnimation(toRemove.Opacity, 0, TimeSpan.FromMilliseconds(160)); anim.Completed += (_, _) => RemoveTranscriptElement(toRemove); toRemove.BeginAnimation(UIElement.OpacityProperty, anim); return; } RemoveTranscriptElement(toRemove); } }