Files
AX-Copilot-Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs
lacvet c4d050f2bf
Some checks failed
Release Gate / gate (push) Has been cancelled
권한·도구 결과 callout 상태 제목 강화
transcript callout이 상태별로 다른 제목과 강조선을 가지도록 정리했습니다.

권한 요청은 확인 포인트와 적용 내용, 도구 결과는 승인 필요, 오류 확인, 부분 완료 점검, 다음 권장 작업으로 구분되게 바꿨습니다.

README와 DEVELOPMENT 문서를 갱신했고 dotnet build 기준 경고 0 / 오류 0을 확인했습니다.
2026-04-06 13:54:31 +09:00

784 lines
34 KiB
C#

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");
}
}