using System.Windows; using System.Windows.Controls; using System.Windows.Media; using AxCopilot.Services; namespace AxCopilot.Views; /// /// 에이전트 파일 수정 시 변경 전/후 diff를 보여주는 패널. /// DiffService의 결과를 색상 하이라이트로 렌더링하고 /// Accept/Reject 버튼으로 변경 승인/취소를 지원합니다. /// public class DiffViewerPanel : Border { private readonly string _filePath; private readonly string _oldContent; private readonly string _newContent; private readonly List _diffLines; /// 사용자가 Accept를 눌렀을 때 발생합니다. public event EventHandler? Accepted; /// 사용자가 Reject를 눌렀을 때 발생합니다. public event EventHandler? Rejected; public DiffViewerPanel(string filePath, string oldContent, string newContent) { _filePath = filePath; _oldContent = oldContent; _newContent = newContent; _diffLines = DiffService.ComputeDiff(oldContent, newContent); CornerRadius = new CornerRadius(10); Background = new SolidColorBrush(Color.FromRgb(0xFA, 0xFA, 0xFF)); BorderBrush = new SolidColorBrush(Color.FromRgb(0xE0, 0xE0, 0xEC)); BorderThickness = new Thickness(1); Padding = new Thickness(0); Margin = new Thickness(0, 8, 0, 8); Child = BuildContent(); } private UIElement BuildContent() { var mainStack = new StackPanel(); // 헤더 var header = new Border { Background = new SolidColorBrush(Color.FromRgb(0xF0, 0xF2, 0xFF)), CornerRadius = new CornerRadius(10, 10, 0, 0), Padding = new Thickness(14, 10, 14, 10), }; var headerGrid = new Grid(); headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); var summary = DiffService.GetSummary(_diffLines); var titlePanel = new StackPanel { Orientation = Orientation.Horizontal }; titlePanel.Children.Add(new TextBlock { Text = "\uE89A", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 13, Foreground = new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC)), VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0), }); titlePanel.Children.Add(new TextBlock { Text = System.IO.Path.GetFileName(_filePath), FontSize = 13, FontWeight = FontWeights.SemiBold, Foreground = new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)), VerticalAlignment = VerticalAlignment.Center, }); titlePanel.Children.Add(new TextBlock { Text = $" · {summary}", FontSize = 11, Foreground = new SolidColorBrush(Color.FromRgb(0x88, 0x88, 0xAA)), VerticalAlignment = VerticalAlignment.Center, }); Grid.SetColumn(titlePanel, 0); headerGrid.Children.Add(titlePanel); // Accept / Reject 버튼 var btnPanel = new StackPanel { Orientation = Orientation.Horizontal }; var acceptBtn = CreateButton("적용", Color.FromRgb(0x10, 0x7C, 0x10), Colors.White); acceptBtn.Click += (_, _) => Accepted?.Invoke(this, EventArgs.Empty); btnPanel.Children.Add(acceptBtn); var rejectBtn = CreateButton("취소", Colors.Transparent, Color.FromRgb(0xC5, 0x0F, 0x1F)); rejectBtn.Click += (_, _) => Rejected?.Invoke(this, EventArgs.Empty); btnPanel.Children.Add(rejectBtn); Grid.SetColumn(btnPanel, 1); headerGrid.Children.Add(btnPanel); header.Child = headerGrid; mainStack.Children.Add(header); // Diff 내용 var scrollViewer = new ScrollViewer { MaxHeight = 300, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, }; var diffStack = new StackPanel { Margin = new Thickness(0) }; foreach (var line in _diffLines) { var (bg, fg, prefix) = line.Type switch { DiffService.DiffType.Added => ( Color.FromRgb(0xE6, 0xFF, 0xED), Color.FromRgb(0x16, 0x6C, 0x34), "+"), DiffService.DiffType.Removed => ( Color.FromRgb(0xFF, 0xEB, 0xE9), Color.FromRgb(0xCF, 0x22, 0x2E), "-"), _ => ( Color.FromRgb(0xFF, 0xFF, 0xFF), Color.FromRgb(0x66, 0x66, 0x88), " ") }; var lineNo = line.OldLineNo?.ToString() ?? ""; var newLineNo = line.NewLineNo?.ToString() ?? ""; var linePanel = new Border { Background = new SolidColorBrush(bg), Padding = new Thickness(8, 1, 8, 1), }; var lineGrid = new Grid(); lineGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(36) }); // old line lineGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(36) }); // new line lineGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(16) }); // prefix lineGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); // content var oldLineText = new TextBlock { Text = lineNo, FontSize = 10, FontFamily = new FontFamily("Consolas"), Foreground = new SolidColorBrush(Color.FromRgb(0xAA, 0xAA, 0xCC)), HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 0, 4, 0), }; Grid.SetColumn(oldLineText, 0); lineGrid.Children.Add(oldLineText); var newLineText = new TextBlock { Text = newLineNo, FontSize = 10, FontFamily = new FontFamily("Consolas"), Foreground = new SolidColorBrush(Color.FromRgb(0xAA, 0xAA, 0xCC)), HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 0, 4, 0), }; Grid.SetColumn(newLineText, 1); lineGrid.Children.Add(newLineText); var prefixText = new TextBlock { Text = prefix, FontSize = 11, FontFamily = new FontFamily("Consolas"), Foreground = new SolidColorBrush(fg), FontWeight = FontWeights.Bold, }; Grid.SetColumn(prefixText, 2); lineGrid.Children.Add(prefixText); var contentText = new TextBlock { Text = line.Content, FontSize = 11, FontFamily = new FontFamily("Consolas"), Foreground = new SolidColorBrush(fg), TextWrapping = TextWrapping.NoWrap, }; Grid.SetColumn(contentText, 3); lineGrid.Children.Add(contentText); linePanel.Child = lineGrid; diffStack.Children.Add(linePanel); } scrollViewer.Content = diffStack; mainStack.Children.Add(scrollViewer); return mainStack; } private static Button CreateButton(string text, Color bg, Color fg) { var btn = new Button { Cursor = System.Windows.Input.Cursors.Hand, Margin = new Thickness(4, 0, 0, 0) }; var template = new ControlTemplate(typeof(Button)); var bdFactory = new FrameworkElementFactory(typeof(Border)); bdFactory.SetValue(Border.BackgroundProperty, new SolidColorBrush(bg)); bdFactory.SetValue(Border.CornerRadiusProperty, new CornerRadius(6)); bdFactory.SetValue(Border.PaddingProperty, new Thickness(12, 5, 12, 5)); if (bg == Colors.Transparent) { bdFactory.SetValue(Border.BorderBrushProperty, new SolidColorBrush(fg)); bdFactory.SetValue(Border.BorderThicknessProperty, new Thickness(1)); } var cpFactory = new FrameworkElementFactory(typeof(ContentPresenter)); cpFactory.SetValue(ContentPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); bdFactory.AppendChild(cpFactory); template.VisualTree = bdFactory; btn.Template = template; btn.Content = new TextBlock { Text = text, FontSize = 11, Foreground = new SolidColorBrush(fg), FontWeight = FontWeights.SemiBold }; return btn; } }