using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using AxCopilot.Services.Agent; namespace AxCopilot.Views; public partial class ChatWindow { private Border CreateCompactEventPill(string summary, Brush primaryText, Brush secondaryText, Brush hintBg, Brush borderBrush, Brush accentBrush) { return new Border { Background = Brushes.Transparent, BorderBrush = Brushes.Transparent, BorderThickness = new Thickness(0), CornerRadius = new CornerRadius(999), Padding = new Thickness(6, 2, 6, 2), Margin = new Thickness(8, 2, 220, 2), HorizontalAlignment = HorizontalAlignment.Left, Child = new StackPanel { Orientation = Orientation.Horizontal, Children = { new TextBlock { Text = "\uE9CE", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 8, Foreground = accentBrush, VerticalAlignment = VerticalAlignment.Center, }, new TextBlock { Text = summary, FontSize = 8.75, Foreground = secondaryText, Margin = new Thickness(4, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, } } } }; } private Border CreateAgentMetaChip(string text, string icon, Brush foreground, Brush background, Brush borderBrush) { return new Border { Background = background, BorderBrush = borderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(999), Padding = new Thickness(5, 1.5, 5, 1.5), Margin = new Thickness(0, 0, 4, 0), Child = new StackPanel { Orientation = Orientation.Horizontal, Children = { new TextBlock { Text = icon, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 7.5, Foreground = foreground, VerticalAlignment = VerticalAlignment.Center, }, new TextBlock { Text = text, FontSize = 7.75, Foreground = foreground, Margin = new Thickness(3, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, } } } }; } private Border CreateAgentMetaCallout(string title, string body, Brush titleBrush, Brush background, Brush borderBrush) { var accentColor = (titleBrush as SolidColorBrush)?.Color ?? Colors.SteelBlue; return new Border { Background = background, BorderBrush = borderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(8), Padding = new Thickness(7, 5, 7, 5), Margin = new Thickness(11, 2, 0, 0), Child = new Grid { ColumnDefinitions = { new ColumnDefinition { Width = new GridLength(3) }, new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, }, Children = { new Border { Background = new SolidColorBrush(accentColor), CornerRadius = new CornerRadius(2), }, new StackPanel { Margin = new Thickness(8, 0, 0, 0), Children = { new TextBlock { Text = title, FontSize = 7.75, FontWeight = FontWeights.SemiBold, Foreground = titleBrush, }, new TextBlock { Text = body, FontSize = 8.1, Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 1, 0, 0), } } } } } }; } private static string GetToolResultCalloutTitle(string statusKind) { return statusKind switch { "error" => "오류 확인", "reject" => "거부됨", "cancel" => "중단됨", "approval_required" => "승인 필요", "partial" => "부분 완료 점검", _ => "다음 권장 작업", }; } private Border CreateAgentInlineActionButton(string text, string icon, Brush foreground, Brush borderBrush, Action onClick) { var button = new Border { Background = Brushes.Transparent, BorderBrush = borderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(999), Padding = new Thickness(6, 2.5, 6, 2.5), Margin = new Thickness(11, 3, 0, 0), Cursor = Cursors.Hand, Child = new StackPanel { Orientation = Orientation.Horizontal, Children = { new TextBlock { Text = icon, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 8, Foreground = foreground, VerticalAlignment = VerticalAlignment.Center, }, new TextBlock { Text = text, FontSize = 8.15, Foreground = foreground, Margin = new Thickness(4, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, } } } }; button.MouseEnter += (_, _) => button.Background = new SolidColorBrush(Color.FromArgb(0x10, 0xFF, 0xFF, 0xFF)); button.MouseLeave += (_, _) => button.Background = Brushes.Transparent; button.MouseLeftButtonUp += (_, e) => { e.Handled = true; onClick(); }; return button; } private static string GetPermissionKindLabel(string kind) { return kind switch { "bash" => "Bash", "powershell" => "PowerShell", "command" => "명령 실행", "web_fetch" => "웹 요청", "mcp" => "MCP", "skill" => "스킬", "question" => "의견 요청", "file_edit" => "파일 수정", "file_write" => "파일 쓰기", "git" => "Git", "document" => "문서 작업", "filesystem" => "파일 접근", _ => "권한 요청", }; } private static string GetToolResultKindLabel(string kind) { return kind switch { "file_edit" => "파일 수정", "file_write" => "파일 쓰기", "filesystem" => "파일 탐색", "file" => "파일 작업", "build_test" => "빌드/테스트", "git" => "Git", "document" => "문서", "skill" => "스킬", "mcp" => "MCP", "question" => "의견 요청", "web" => "웹 요청", "command" => "명령 실행", _ => "도구 결과", }; } private void AppendAgentEventPresentationMeta( StackPanel stack, AgentEvent evt, PermissionRequestPresentation? permissionPresentation, ToolResultPresentation? toolResultPresentation, Brush secondaryText, Brush hintBg, Brush borderColor) { string? guidance = null; var chipRow = new WrapPanel { Margin = new Thickness(11, 2, 0, 0), Orientation = Orientation.Horizontal, }; if (permissionPresentation != null) { guidance = permissionPresentation.ActionHint; chipRow.Children.Add(CreateAgentMetaChip( GetPermissionKindLabel(permissionPresentation.Kind), "\uE8A5", BrushFromHex("#475569"), BrushFromHex("#F8FAFC"), BrushFromHex("#E2E8F0"))); if (permissionPresentation.RequiresPreview) chipRow.Children.Add(CreateAgentMetaChip( "미리보기 권장", "\uE8A7", BrushFromHex("#1D4ED8"), BrushFromHex("#EFF6FF"), BrushFromHex("#BFDBFE"))); if (evt.Type == AgentEventType.PermissionRequest) { if (string.Equals(permissionPresentation.Severity, "high", StringComparison.OrdinalIgnoreCase)) chipRow.Children.Add(CreateAgentMetaChip( "주의 필요", "\uE814", BrushFromHex("#B91C1C"), BrushFromHex("#FEF2F2"), BrushFromHex("#FECACA"))); else if (string.Equals(permissionPresentation.Severity, "medium", StringComparison.OrdinalIgnoreCase)) chipRow.Children.Add(CreateAgentMetaChip( "검토 권장", "\uE946", BrushFromHex("#A16207"), BrushFromHex("#FFFBEB"), BrushFromHex("#FDE68A"))); } } if (toolResultPresentation != null) { guidance = toolResultPresentation.FollowUpHint; chipRow.Children.Add(CreateAgentMetaChip( GetToolResultKindLabel(toolResultPresentation.Kind), "\uE9CE", BrushFromHex("#475569"), BrushFromHex("#F8FAFC"), BrushFromHex("#E2E8F0"))); if (toolResultPresentation.NeedsAttention) chipRow.Children.Add(CreateAgentMetaChip( "확인 필요", "\uE814", BrushFromHex("#B91C1C"), BrushFromHex("#FEF2F2"), BrushFromHex("#FECACA"))); if (string.Equals(toolResultPresentation.StatusKind, "approval_required", StringComparison.OrdinalIgnoreCase)) chipRow.Children.Add(CreateAgentMetaChip( "승인 후 계속", "\uE8D7", BrushFromHex("#C2410C"), BrushFromHex("#FFF7ED"), BrushFromHex("#FED7AA"))); else if (string.Equals(toolResultPresentation.StatusKind, "partial", StringComparison.OrdinalIgnoreCase)) chipRow.Children.Add(CreateAgentMetaChip( "후속 점검", "\uE7BA", BrushFromHex("#A16207"), BrushFromHex("#FFFBEB"), BrushFromHex("#FDE68A"))); } if (!string.IsNullOrWhiteSpace(guidance)) { var clipped = guidance.Length > 92 ? guidance[..92] + "..." : guidance; if (permissionPresentation != null) { var titleBrush = string.Equals(permissionPresentation.Severity, "high", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#B91C1C") : string.Equals(permissionPresentation.Severity, "medium", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#A16207") : BrushFromHex("#2563EB"); var background = string.Equals(permissionPresentation.Severity, "high", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FFF7F7") : string.Equals(permissionPresentation.Severity, "medium", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FFFBEB") : BrushFromHex("#EFF6FF"); var border = string.Equals(permissionPresentation.Severity, "high", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FECACA") : string.Equals(permissionPresentation.Severity, "medium", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FDE68A") : BrushFromHex("#BFDBFE"); var title = evt.Type == AgentEventType.PermissionGranted ? "적용 내용" : "확인 포인트"; stack.Children.Add(CreateAgentMetaCallout(title, clipped, titleBrush, background, border)); } else if (toolResultPresentation != null) { var titleBrush = string.Equals(toolResultPresentation.StatusKind, "error", StringComparison.OrdinalIgnoreCase) || string.Equals(toolResultPresentation.StatusKind, "reject", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#B91C1C") : string.Equals(toolResultPresentation.StatusKind, "approval_required", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#C2410C") : BrushFromHex("#A16207"); var background = string.Equals(toolResultPresentation.StatusKind, "error", StringComparison.OrdinalIgnoreCase) || string.Equals(toolResultPresentation.StatusKind, "reject", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FFF7F7") : string.Equals(toolResultPresentation.StatusKind, "approval_required", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FFF7ED") : BrushFromHex("#FFFBEB"); var border = string.Equals(toolResultPresentation.StatusKind, "error", StringComparison.OrdinalIgnoreCase) || string.Equals(toolResultPresentation.StatusKind, "reject", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FECACA") : string.Equals(toolResultPresentation.StatusKind, "approval_required", StringComparison.OrdinalIgnoreCase) ? BrushFromHex("#FED7AA") : BrushFromHex("#FDE68A"); stack.Children.Add(CreateAgentMetaCallout(GetToolResultCalloutTitle(toolResultPresentation.StatusKind), clipped, titleBrush, background, border)); } } if (chipRow.Children.Count > 0) stack.Children.Add(chipRow); if (!string.IsNullOrWhiteSpace(evt.FilePath) && System.IO.File.Exists(evt.FilePath) && ((permissionPresentation?.RequiresPreview ?? false) || toolResultPresentation != null)) { var previewPath = evt.FilePath!; stack.Children.Add(CreateAgentInlineActionButton( "프리뷰 열기", "\uE8A1", BrushFromHex("#2563EB"), BrushFromHex("#BFDBFE"), () => ShowPreviewPanel(previewPath))); } } private void AddAgentEventBanner(AgentEvent evt) { var logLevel = _settings.Settings.Llm.AgentLogLevel; if (evt.Type == AgentEventType.Planning && evt.Steps is { Count: > 0 }) { var compactPrimaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var compactSecondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var compactHintBg = TryFindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var compactBorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var compactAccentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; var summary = !string.IsNullOrWhiteSpace(evt.Summary) ? evt.Summary! : $"계획 {evt.Steps.Count}단계"; var pill = CreateCompactEventPill(summary, compactPrimaryText, compactSecondaryText, compactHintBg, compactBorderBrush, compactAccentBrush); pill.Opacity = 0; pill.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160))); MessagePanel.Children.Add(pill); return; } if (evt.Type == AgentEventType.StepStart && evt.StepTotal > 0) { UpdateProgressBar(evt); return; } if (evt.Type == AgentEventType.Thinking && ContainsAny(evt.Summary ?? "", "컨텍스트 압축", "microcompact", "session memory", "compact")) { var compactPrimaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var compactSecondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var compactHintBg = TryFindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var compactBorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var compactAccentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; var pill = CreateCompactEventPill(string.IsNullOrWhiteSpace(evt.Summary) ? "컨텍스트 압축" : evt.Summary, compactPrimaryText, compactSecondaryText, compactHintBg, compactBorderBrush, compactAccentBrush); pill.Opacity = 0; pill.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160))); MessagePanel.Children.Add(pill); return; } if (!string.Equals(logLevel, "debug", StringComparison.OrdinalIgnoreCase) && evt.Type is AgentEventType.Paused or AgentEventType.Resumed) return; if (!string.Equals(logLevel, "debug", StringComparison.OrdinalIgnoreCase) && evt.Type == AgentEventType.ToolCall) return; var isTotalStats = evt.Type == AgentEventType.StepDone && evt.ToolName == "total_stats"; var transcriptBadgeLabel = GetTranscriptBadgeLabel(evt); var permissionPresentation = evt.Type switch { AgentEventType.PermissionRequest => PermissionRequestPresentationCatalog.Resolve(evt.ToolName, pending: true), AgentEventType.PermissionGranted => PermissionRequestPresentationCatalog.Resolve(evt.ToolName, pending: false), _ => null }; var toolResultPresentation = evt.Type == AgentEventType.ToolResult ? ToolResultPresentationCatalog.Resolve(evt, transcriptBadgeLabel) : null; var (icon, label, bgHex, fgHex) = isTotalStats ? ("\uE9D2", "전체 통계", "#F3EEFF", "#7C3AED") : evt.Type switch { AgentEventType.Thinking => ("\uE8BD", "분석 중", "#F0F0FF", "#6B7BC4"), AgentEventType.PermissionRequest => (permissionPresentation!.Icon, permissionPresentation.Label, permissionPresentation.BackgroundHex, permissionPresentation.ForegroundHex), AgentEventType.PermissionGranted => (permissionPresentation!.Icon, permissionPresentation.Label, permissionPresentation.BackgroundHex, permissionPresentation.ForegroundHex), AgentEventType.PermissionDenied => ("\uE783", "권한 거부", "#FEF2F2", "#DC2626"), AgentEventType.Decision => GetDecisionBadgeMeta(evt.Summary), AgentEventType.ToolCall => ("\uE8A7", transcriptBadgeLabel, "#EEF6FF", "#3B82F6"), AgentEventType.ToolResult => (toolResultPresentation!.Icon, toolResultPresentation.Label, toolResultPresentation.BackgroundHex, toolResultPresentation.ForegroundHex), AgentEventType.SkillCall => ("\uE8A5", transcriptBadgeLabel, "#FFF7ED", "#EA580C"), AgentEventType.Error => ("\uE783", "오류", "#FEF2F2", "#DC2626"), AgentEventType.Complete => ("\uE930", "완료", "#F0FFF4", "#15803D"), AgentEventType.StepDone => ("\uE73E", "단계 완료", "#EEF9EE", "#16A34A"), AgentEventType.Paused => ("\uE769", "일시정지", "#FFFBEB", "#D97706"), AgentEventType.Resumed => ("\uE768", "재개", "#ECFDF5", "#059669"), _ => ("\uE946", "에이전트", "#F5F5F5", "#6B7280"), }; var itemDisplayName = evt.Type == AgentEventType.SkillCall ? GetAgentItemDisplayName(evt.ToolName, slashPrefix: true) : GetAgentItemDisplayName(evt.ToolName); var eventSummaryText = BuildAgentEventSummaryText(evt, itemDisplayName); if (string.IsNullOrWhiteSpace(eventSummaryText)) { eventSummaryText = evt.Type switch { AgentEventType.PermissionRequest or AgentEventType.PermissionGranted => permissionPresentation?.Description ?? "", AgentEventType.ToolResult => toolResultPresentation?.Description ?? "", _ => "" }; } var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray; var hintBg = TryFindResource("HintBackground") as Brush ?? BrushFromHex("#F8FAFC"); var borderColor = TryFindResource("BorderColor") as Brush ?? BrushFromHex("#E2E8F0"); var accentBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(fgHex)); var banner = new Border { Background = Brushes.Transparent, BorderBrush = Brushes.Transparent, BorderThickness = new Thickness(0), CornerRadius = new CornerRadius(0), Padding = new Thickness(0), Margin = new Thickness(12, 0, 12, 1), HorizontalAlignment = HorizontalAlignment.Stretch, }; if (!string.IsNullOrWhiteSpace(evt.RunId)) _runBannerAnchors[evt.RunId] = banner; var sp = new StackPanel(); var headerGrid = new Grid(); headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); var headerLeft = new StackPanel { Orientation = Orientation.Horizontal }; headerLeft.Children.Add(new TextBlock { Text = icon, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 8.25, Foreground = accentBrush, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 3, 0), }); headerLeft.Children.Add(new TextBlock { Text = label, FontSize = 8.25, FontWeight = FontWeights.Medium, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, }); if (IsTranscriptToolLikeEvent(evt) && !string.IsNullOrWhiteSpace(evt.ToolName)) { headerLeft.Children.Add(new TextBlock { Text = $" · {itemDisplayName}", FontSize = 8.25, FontWeight = FontWeights.SemiBold, Foreground = primaryText, VerticalAlignment = VerticalAlignment.Center, }); } Grid.SetColumn(headerLeft, 0); var headerRight = new StackPanel { Orientation = Orientation.Horizontal }; if (logLevel != "simple" && evt.ElapsedMs > 0) { headerRight.Children.Add(new TextBlock { Text = evt.ElapsedMs < 1000 ? $"{evt.ElapsedMs}ms" : $"{evt.ElapsedMs / 1000.0:F1}s", FontSize = 7.5, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(3, 0, 0, 0), }); } if (logLevel != "simple" && (evt.InputTokens > 0 || evt.OutputTokens > 0)) { var tokenText = evt.InputTokens > 0 && evt.OutputTokens > 0 ? $"{evt.InputTokens}→{evt.OutputTokens}t" : evt.InputTokens > 0 ? $"↑{evt.InputTokens}t" : $"↓{evt.OutputTokens}t"; headerRight.Children.Add(new Border { Background = hintBg, BorderBrush = borderColor, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(999), Padding = new Thickness(3.5, 1, 3.5, 1), Margin = new Thickness(3, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, Child = new TextBlock { Text = tokenText, FontSize = 7.25, Foreground = secondaryText, FontFamily = new FontFamily("Consolas"), }, }); } Grid.SetColumn(headerRight, 1); headerGrid.Children.Add(headerLeft); headerGrid.Children.Add(headerRight); sp.Children.Add(headerGrid); if (logLevel == "simple") { if (!string.IsNullOrEmpty(eventSummaryText)) { var shortSummary = eventSummaryText.Length > 100 ? eventSummaryText[..100] + "…" : eventSummaryText; sp.Children.Add(new TextBlock { Text = shortSummary, FontSize = 8.4, Foreground = secondaryText, TextWrapping = TextWrapping.NoWrap, TextTrimming = TextTrimming.CharacterEllipsis, Margin = new Thickness(11, 1, 0, 0), }); } } else if (!string.IsNullOrEmpty(eventSummaryText)) { var summaryText = eventSummaryText.Length > 92 ? eventSummaryText[..92] + "…" : eventSummaryText; sp.Children.Add(new TextBlock { Text = summaryText, FontSize = 8.4, Foreground = secondaryText, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(11, 1, 0, 0), }); } if (permissionPresentation != null || toolResultPresentation != null) { AppendAgentEventPresentationMeta( sp, evt, permissionPresentation, toolResultPresentation, secondaryText, hintBg, borderColor); } var reviewChipRow = BuildReviewSignalChipRow( kind: null, toolName: evt.ToolName, title: label, summary: evt.Summary); if (reviewChipRow != null && string.Equals(logLevel, "debug", StringComparison.OrdinalIgnoreCase)) { reviewChipRow.Margin = new Thickness(12, 2, 0, 0); sp.Children.Add(reviewChipRow); } if (logLevel == "debug" && !string.IsNullOrEmpty(evt.ToolInput)) { sp.Children.Add(new Border { Background = hintBg, BorderBrush = borderColor, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(8), Padding = new Thickness(5, 3, 5, 3), Margin = new Thickness(12, 2, 0, 0), Child = new TextBlock { Text = evt.ToolInput.Length > 240 ? evt.ToolInput[..240] + "…" : evt.ToolInput, FontSize = 8.5, Foreground = secondaryText, FontFamily = new FontFamily("Consolas"), TextWrapping = TextWrapping.Wrap, }, }); } if (!string.IsNullOrEmpty(evt.FilePath)) { var fileName = System.IO.Path.GetFileName(evt.FilePath); var dirName = System.IO.Path.GetDirectoryName(evt.FilePath) ?? ""; if (!string.Equals(logLevel, "debug", StringComparison.OrdinalIgnoreCase)) { var compactPathRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(12, 1.5, 0, 0), ToolTip = evt.FilePath, }; compactPathRow.Children.Add(new TextBlock { Text = "\uE8B7", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 8, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 3, 0), }); compactPathRow.Children.Add(new TextBlock { Text = string.IsNullOrWhiteSpace(fileName) ? evt.FilePath : fileName, FontSize = 8.5, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, }); sp.Children.Add(compactPathRow); } else { var pathBorder = new Border { Background = hintBg, BorderBrush = borderColor, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(8), Padding = new Thickness(7, 4, 7, 4), Margin = new Thickness(12, 2, 0, 0), }; var pathGrid = new Grid(); pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); var left = new StackPanel { Orientation = Orientation.Vertical }; var topRow = new StackPanel { Orientation = Orientation.Horizontal }; topRow.Children.Add(new TextBlock { Text = "\uE8B7", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 10, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9CA3AF")), VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 4, 0), }); topRow.Children.Add(new TextBlock { Text = string.IsNullOrWhiteSpace(fileName) ? evt.FilePath : fileName, FontSize = 10, FontWeight = FontWeights.SemiBold, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#374151")), VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, }); left.Children.Add(topRow); if (!string.IsNullOrWhiteSpace(dirName)) { left.Children.Add(new TextBlock { Text = dirName, FontSize = 9, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9CA3AF")), FontFamily = new FontFamily("Consolas"), VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, }); } Grid.SetColumn(left, 0); pathGrid.Children.Add(left); var quickActions = BuildFileQuickActions(evt.FilePath); Grid.SetColumn(quickActions, 1); pathGrid.Children.Add(quickActions); pathBorder.Child = pathGrid; sp.Children.Add(pathBorder); } } banner.Child = sp; if (isTotalStats) { banner.Cursor = Cursors.Hand; banner.ToolTip = "클릭하여 병목 분석 보기"; banner.MouseLeftButtonUp += (_, _) => { OpenWorkflowAnalyzerIfEnabled(); _analyzerWindow?.SwitchToBottleneckTab(); _analyzerWindow?.Activate(); }; } banner.Opacity = 0; banner.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200))); MessagePanel.Children.Add(banner); } private static (string icon, string label, string bgHex, string fgHex) GetDecisionBadgeMeta(string? summary) { if (IsDecisionApproved(summary)) return ("\uE73E", "계획 승인", "#ECFDF5", "#059669"); if (IsDecisionRejected(summary)) return ("\uE783", "계획 반려", "#FEF2F2", "#DC2626"); return ("\uE70F", "계획 확인", "#FFF7ED", "#C2410C"); } }