using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using AxCopilot.Models; using AxCopilot.Services; using AxCopilot.Services.Agent; namespace AxCopilot.Views; public partial class ChatWindow { private void BtnPermission_Click(object sender, RoutedEventArgs e) { if (PermissionPopup == null) return; PermissionItems.Children.Clear(); ChatConversation? currentConversation; lock (_convLock) currentConversation = _currentConversation; var coreLevels = PermissionModePresentationCatalog.Ordered.ToList(); var current = PermissionModeCatalog.NormalizeGlobalMode(_settings.Settings.Llm.FilePermission); void AddPermissionRows(Panel container, IEnumerable levels) { var hoverBackground = TryFindResource("ItemHoverBackground") as Brush ?? BrushFromHex("#F8FAFC"); var selectedBackground = TryFindResource("HintBackground") as Brush ?? BrushFromHex("#F8FAFC"); var selectedBorder = TryFindResource("AccentColor") as Brush ?? BrushFromHex("#D6E4FF"); foreach (var item in levels) { var level = item.Mode; var isActive = level.Equals(current, StringComparison.OrdinalIgnoreCase); var rowBorder = new Border { Background = isActive ? selectedBackground : Brushes.Transparent, BorderBrush = isActive ? selectedBorder : Brushes.Transparent, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(12), Padding = new Thickness(10, 10, 10, 10), Margin = new Thickness(0, 0, 0, 4), Cursor = Cursors.Hand, Focusable = true, }; KeyboardNavigation.SetIsTabStop(rowBorder, true); var row = new Grid(); row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); row.Children.Add(new TextBlock { Text = item.Icon, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 15, Foreground = BrushFromHex(item.ColorHex), Margin = new Thickness(0, 0, 10, 0), VerticalAlignment = VerticalAlignment.Center, }); var textStack = new StackPanel(); textStack.Children.Add(new TextBlock { Text = item.Title, FontSize = 13.5, FontWeight = FontWeights.SemiBold, Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.White, }); textStack.Children.Add(new TextBlock { Text = item.Description, FontSize = 11.5, Margin = new Thickness(0, 2, 0, 0), Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray, TextWrapping = TextWrapping.Wrap, LineHeight = 16, MaxWidth = 220, }); Grid.SetColumn(textStack, 1); row.Children.Add(textStack); var check = new TextBlock { Text = isActive ? "\uE73E" : "", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 12, FontWeight = FontWeights.Bold, Foreground = BrushFromHex("#2563EB"), VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(12, 0, 0, 0), }; Grid.SetColumn(check, 2); row.Children.Add(check); rowBorder.Child = row; rowBorder.MouseEnter += (_, _) => rowBorder.Background = isActive ? selectedBackground : hoverBackground; rowBorder.MouseLeave += (_, _) => rowBorder.Background = isActive ? selectedBackground : Brushes.Transparent; var capturedLevel = level; void ApplyPermission() { _settings.Settings.Llm.FilePermission = PermissionModeCatalog.NormalizeGlobalMode(capturedLevel); try { _settings.Save(); } catch { } _appState.LoadFromSettings(_settings); UpdatePermissionUI(); SaveConversationSettings(); RefreshInlineSettingsPanel(); RefreshOverlayModeButtons(); PermissionPopup.IsOpen = false; } rowBorder.MouseLeftButtonDown += (_, _) => ApplyPermission(); rowBorder.KeyDown += (_, ke) => { if (ke.Key is Key.Enter or Key.Space) { ke.Handled = true; ApplyPermission(); } }; container.Children.Add(rowBorder); } } AddPermissionRows(PermissionItems, coreLevels); PermissionPopup.IsOpen = true; Dispatcher.BeginInvoke(() => { TryFocusFirstPermissionElement(PermissionItems); }, System.Windows.Threading.DispatcherPriority.Input); } private static bool TryFocusFirstPermissionElement(DependencyObject root) { if (root is UIElement ui && ui.Focusable && ui.IsEnabled && ui.Visibility == Visibility.Visible) return ui.Focus(); var childCount = VisualTreeHelper.GetChildrenCount(root); for (var i = 0; i < childCount; i++) { var child = VisualTreeHelper.GetChild(root, i); if (TryFocusFirstPermissionElement(child)) return true; } return false; } private void SetToolPermissionOverride(string toolName, string? mode) { if (string.IsNullOrWhiteSpace(toolName)) return; var toolPermissions = _settings.Settings.Llm.ToolPermissions ??= new Dictionary(StringComparer.OrdinalIgnoreCase); var existingKey = toolPermissions.Keys.FirstOrDefault(x => string.Equals(x, toolName, StringComparison.OrdinalIgnoreCase)); if (string.IsNullOrWhiteSpace(mode)) { if (!string.IsNullOrWhiteSpace(existingKey)) toolPermissions.Remove(existingKey!); } else { toolPermissions[existingKey ?? toolName] = PermissionModeCatalog.NormalizeToolOverride(mode); } try { _settings.Save(); } catch { } _appState.LoadFromSettings(_settings); UpdatePermissionUI(); SaveConversationSettings(); } private void RefreshPermissionPopup() { if (PermissionPopup == null) return; BtnPermission_Click(this, new RoutedEventArgs()); } private bool GetPermissionPopupSectionExpanded(string sectionKey, bool defaultValue = false) { var map = _settings.Settings.Llm.PermissionPopupSections; if (map != null && map.TryGetValue(sectionKey, out var expanded)) return expanded; return defaultValue; } private void SetPermissionPopupSectionExpanded(string sectionKey, bool expanded) { var map = _settings.Settings.Llm.PermissionPopupSections ??= new Dictionary(StringComparer.OrdinalIgnoreCase); map[sectionKey] = expanded; try { _settings.Save(); } catch { } } private void BtnPermissionTopBannerClose_Click(object sender, RoutedEventArgs e) { if (PermissionTopBanner != null) PermissionTopBanner.Visibility = Visibility.Collapsed; } private void UpdatePermissionUI() { if (PermissionLabel == null || PermissionIcon == null) return; ChatConversation? currentConversation; lock (_convLock) currentConversation = _currentConversation; var summary = _appState.GetPermissionSummary(currentConversation); var perm = PermissionModeCatalog.NormalizeGlobalMode(summary.EffectiveMode); PermissionLabel.Text = PermissionModeCatalog.ToDisplayLabel(perm); PermissionIcon.Text = perm switch { "AcceptEdits" => "\uE73E", "BypassPermissions" => "\uE7BA", "Deny" => "\uE711", _ => "\uE8D7", }; if (BtnPermission != null) { var operationMode = OperationModePolicy.Normalize(_settings.Settings.OperationMode); BtnPermission.ToolTip = $"{summary.Description}\n운영 모드: {operationMode}\n기본값: {PermissionModeCatalog.ToDisplayLabel(summary.DefaultMode)} · 예외 {summary.OverrideCount}개"; BtnPermission.Background = Brushes.Transparent; BtnPermission.BorderThickness = new Thickness(1); } if (!string.Equals(_lastPermissionBannerMode, perm, StringComparison.OrdinalIgnoreCase)) _lastPermissionBannerMode = perm; if (perm == PermissionModeCatalog.AcceptEdits) { var activeColor = new SolidColorBrush(Color.FromRgb(0x10, 0x7C, 0x10)); PermissionLabel.Foreground = activeColor; PermissionIcon.Foreground = activeColor; if (BtnPermission != null) BtnPermission.BorderBrush = BrushFromHex("#86EFAC"); if (PermissionTopBanner != null) { PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC"); PermissionTopBannerIcon.Text = "\uE73E"; PermissionTopBannerIcon.Foreground = activeColor; PermissionTopBannerTitle.Text = "현재 권한 모드 · 편집 자동 승인"; PermissionTopBannerTitle.Foreground = BrushFromHex("#166534"); PermissionTopBannerText.Text = "모든 파일 편집은 자동 승인하고, 명령 실행만 계속 확인합니다."; PermissionTopBanner.Visibility = Visibility.Collapsed; } } else if (perm == PermissionModeCatalog.Deny) { var denyColor = new SolidColorBrush(Color.FromRgb(0x10, 0x7C, 0x10)); PermissionLabel.Foreground = denyColor; PermissionIcon.Foreground = denyColor; if (BtnPermission != null) BtnPermission.BorderBrush = BrushFromHex("#86EFAC"); if (PermissionTopBanner != null) { PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC"); PermissionTopBannerIcon.Text = "\uE73E"; PermissionTopBannerIcon.Foreground = denyColor; PermissionTopBannerTitle.Text = "현재 권한 모드 · 읽기 전용"; PermissionTopBannerTitle.Foreground = denyColor; PermissionTopBannerText.Text = "파일 읽기만 허용하고 생성, 수정, 삭제는 차단합니다."; PermissionTopBanner.Visibility = Visibility.Collapsed; } } else if (perm == PermissionModeCatalog.BypassPermissions) { var autoColor = new SolidColorBrush(Color.FromRgb(0xC2, 0x41, 0x0C)); PermissionLabel.Foreground = autoColor; PermissionIcon.Foreground = autoColor; if (BtnPermission != null) BtnPermission.BorderBrush = BrushFromHex("#FDBA74"); if (PermissionTopBanner != null) { PermissionTopBanner.BorderBrush = BrushFromHex("#FDBA74"); PermissionTopBannerIcon.Text = "\uE814"; PermissionTopBannerIcon.Foreground = autoColor; PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 건너뛰기"; PermissionTopBannerTitle.Foreground = autoColor; PermissionTopBannerText.Text = "파일 편집과 명령 실행까지 모두 자동 허용합니다. 민감한 작업 전에는 설정을 다시 확인하세요."; PermissionTopBanner.Visibility = Visibility.Collapsed; } } else { var defaultFg = BrushFromHex("#2563EB"); var iconFg = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xEB)); PermissionLabel.Foreground = defaultFg; PermissionIcon.Foreground = iconFg; if (BtnPermission != null) BtnPermission.BorderBrush = BrushFromHex("#BFDBFE"); if (PermissionTopBanner != null) { if (perm == PermissionModeCatalog.Default) { PermissionTopBanner.BorderBrush = BrushFromHex("#BFDBFE"); PermissionTopBannerIcon.Text = "\uE8D7"; PermissionTopBannerIcon.Foreground = BrushFromHex("#1D4ED8"); PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 요청"; PermissionTopBannerTitle.Foreground = BrushFromHex("#1D4ED8"); PermissionTopBannerText.Text = "변경하기 전에 항상 확인합니다."; PermissionTopBanner.Visibility = Visibility.Collapsed; } else { PermissionTopBanner.Visibility = Visibility.Collapsed; } } } } }