재구성 AX Agent 설정과 채팅 UI를 Claude형 구조로
Some checks failed
Release Gate / gate (push) Has been cancelled

This commit is contained in:
2026-04-04 17:48:51 +09:00
parent 90c2f15e96
commit a027ea4f9a
6000 changed files with 11532 additions and 94063 deletions

View File

@@ -0,0 +1,498 @@
<Window x:Class="AxCopilot.Views.AgentSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AX Agent 설정"
Width="780"
Height="760"
MinWidth="700"
MinHeight="580"
WindowStyle="None"
ResizeMode="CanResizeWithGrip"
Background="{DynamicResource LauncherBackground}"
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False">
<Window.Resources>
<Style x:Key="OutlineHoverBtn" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
<Setter Property="Foreground" Value="{DynamicResource PrimaryText}"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Padding" Value="9,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ToggleSwitch" TargetType="CheckBox">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<Grid Width="46" Height="26" VerticalAlignment="Center">
<Border x:Name="Track"
Width="46"
Height="26"
CornerRadius="13"
Background="#D0D0E0"/>
<Ellipse x:Name="Thumb"
Width="20"
Height="20"
Margin="3,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Fill="White">
<Ellipse.Effect>
<DropShadowEffect BlurRadius="5" ShadowDepth="1" Opacity="0.25" Direction="270"/>
</Ellipse.Effect>
</Ellipse>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Track" Property="Background" Value="{DynamicResource AccentColor}"/>
<Setter TargetName="Thumb" Property="HorizontalAlignment" Value="Right"/>
<Setter TargetName="Thumb" Property="Margin" Value="0,0,3,0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Border Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="48"/>
<RowDefinition Height="*"/>
<RowDefinition Height="56"/>
</Grid.RowDefinitions>
<Border Grid.Row="0"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource SeparatorColor}"
BorderThickness="0,0,0,1">
<Grid Margin="14,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="AX Agent 설정"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Border Grid.Column="1"
Cursor="Hand"
Padding="8,4"
CornerRadius="8"
Background="Transparent"
MouseLeftButtonUp="BtnClose_MouseLeftButtonUp">
<TextBlock Text="닫기"
Foreground="{DynamicResource SecondaryText}"
FontSize="12"/>
</Border>
</Grid>
</Border>
<ScrollViewer Grid.Row="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="18,14,18,16">
<TextBlock Text="테마"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<WrapPanel Margin="0,8,0,0">
<Border x:Name="ThemeSystemCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="ThemeSystemCard_MouseLeftButtonUp">
<TextBlock Text="System" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="ThemeLightCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="ThemeLightCard_MouseLeftButtonUp">
<TextBlock Text="Light" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="ThemeDarkCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="ThemeDarkCard_MouseLeftButtonUp">
<TextBlock Text="Dark" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
</WrapPanel>
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
<TextBlock Text="모델 및 연결"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="서비스를 선택하고 모델, 연결 옵션, 운영 모드를 조정합니다."
Margin="0,4,0,10"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
<WrapPanel>
<Border x:Name="SvcOllamaCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="SvcOllamaCard_MouseLeftButtonUp">
<TextBlock Text="Ollama" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="SvcVllmCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="SvcVllmCard_MouseLeftButtonUp">
<TextBlock Text="vLLM" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="SvcGeminiCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="SvcGeminiCard_MouseLeftButtonUp">
<TextBlock Text="Gemini" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="SvcClaudeCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="SvcClaudeCard_MouseLeftButtonUp">
<TextBlock Text="Claude" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
</WrapPanel>
<TextBox x:Name="ModelInput"
Margin="0,6,0,8"
Padding="8,6"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
<WrapPanel x:Name="ModelChipPanel" Margin="0,0,0,8"/>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="vLLM SSL 인증서 검증 우회 허용"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<CheckBox x:Name="ChkVllmAllowInsecureTls"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="운영 모드"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Button x:Name="BtnOperationMode"
Grid.Column="1"
MinWidth="140"
Style="{StaticResource OutlineHoverBtn}"
Click="BtnOperationMode_Click"/>
</Grid>
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
<TextBlock Text="권한 및 실행"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="권한 모드"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Button x:Name="BtnPermissionMode"
Grid.Column="1"
MinWidth="120"
Style="{StaticResource OutlineHoverBtn}"
Click="BtnPermissionMode_Click"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="계획 모드"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Button x:Name="BtnPlanMode"
Grid.Column="1"
MinWidth="120"
Style="{StaticResource OutlineHoverBtn}"
Click="BtnPlanMode_Click"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="추론 강도"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Button x:Name="BtnReasoningMode"
Grid.Column="1"
MinWidth="120"
Style="{StaticResource OutlineHoverBtn}"
Click="BtnReasoningMode_Click"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="폴더 데이터 활용"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Button x:Name="BtnFolderDataUsage"
Grid.Column="1"
MinWidth="120"
Style="{StaticResource OutlineHoverBtn}"
Click="BtnFolderDataUsage_Click"/>
</Grid>
<StackPanel x:Name="AdvancedPanel">
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
<TextBlock Text="컨텍스트 및 오류 관리"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="자동 컨텍스트 압축 사용"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<CheckBox x:Name="ChkEnableProactiveCompact"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="압축 시작 한도(%)"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<TextBox x:Name="TxtContextCompactTriggerPercent"
Grid.Column="1"
Padding="8,5"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="최대 컨텍스트 토큰"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<TextBox x:Name="TxtMaxContextTokens"
Grid.Column="1"
Padding="8,5"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="오류 재시도 횟수"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<TextBox x:Name="TxtMaxRetryOnError"
Grid.Column="1"
Padding="8,5"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</Grid>
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
<TextBlock Text="도구 및 검증"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="스킬 시스템"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
<CheckBox x:Name="ChkEnableSkillSystem"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="도구 훅 사용"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
<CheckBox x:Name="ChkEnableToolHooks"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="입력 변형 반영"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
<CheckBox x:Name="ChkEnableHookInputMutation"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="권한 갱신 반영"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
<CheckBox x:Name="ChkEnableHookPermissionUpdate"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Cowork 검증 강제"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
<CheckBox x:Name="ChkEnableCoworkVerification"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Code 검증 강제"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
<CheckBox x:Name="ChkEnableCodeVerification"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="병렬 도구 실행"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
<CheckBox x:Name="ChkEnableParallelTools"
Grid.Column="1"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
</StackPanel>
</StackPanel>
</ScrollViewer>
<Border Grid.Row="2"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource SeparatorColor}"
BorderThickness="0,1,0,0">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,14,0">
<Button Content="고급 설정 열기"
Style="{StaticResource OutlineHoverBtn}"
Margin="0,0,8,0"
Click="BtnOpenFullSettings_Click"/>
<Button Content="저장"
MinWidth="96"
Margin="0,0,8,0"
Background="{DynamicResource AccentColor}"
Foreground="White"
BorderThickness="0"
Padding="10,6"
Cursor="Hand"
Click="BtnSave_Click"/>
</StackPanel>
</Border>
</Grid>
</Border>
</Window>

View File

@@ -112,6 +112,36 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingsNavBtn" TargetType="RadioButton">
<Setter Property="Foreground" Value="{DynamicResource SecondaryText}"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Padding" Value="14,10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border x:Name="Bd"
Background="Transparent"
CornerRadius="10"
Padding="{TemplateBinding Padding}">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource PrimaryText}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource HintBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource PrimaryText}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 모든 드롭다운 메뉴는 코드-비하인드에서 커스텀 Popup으로 렌더링 -->
</Window.Resources>
@@ -185,7 +215,9 @@
<!-- 좌측: 사이드바 -->
<!-- ══════════════════════════════════════════════════════ -->
<Border x:Name="SidebarPanel" Grid.Column="1"
Background="{DynamicResource ItemBackground}">
Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="0,0,1,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="52"/>
@@ -198,14 +230,14 @@
</Grid.RowDefinitions>
<!-- 헤더: 로고 + 새 대화 -->
<Grid Grid.Row="0" Margin="14,0">
<Grid Grid.Row="0" Margin="16,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Border Background="{DynamicResource AccentColor}" CornerRadius="6"
Width="24" Height="24">
<TextBlock Text="&#xE8BD;" FontFamily="Segoe MDL2 Assets" FontSize="12"
<Border Background="{DynamicResource AccentColor}" CornerRadius="8"
Width="28" Height="28">
<TextBlock Text="&#xE8BD;" FontFamily="Segoe MDL2 Assets" FontSize="14"
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="AX Agent" FontSize="13.5" FontWeight="SemiBold"
<TextBlock Text="AX Agent" FontSize="14.5" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center" Margin="10,0,0,0"/>
</StackPanel>
@@ -219,8 +251,9 @@
</Grid>
<!-- 검색 -->
<Border Grid.Row="1" Background="{DynamicResource HintBackground}"
CornerRadius="10" Margin="12,0,12,6" Padding="10,6">
<Border Grid.Row="1" Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1"
CornerRadius="14" Margin="12,2,12,8" Padding="12,7">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -299,12 +332,12 @@
</Border>
<!-- 탭별 좌측 메뉴 -->
<Border Grid.Row="3" Margin="12,0,12,6"
Background="{DynamicResource HintBackground}"
<Border Grid.Row="3" Margin="12,0,12,8"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="10"
Padding="10,8">
CornerRadius="14"
Padding="12,10">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,4" Visibility="Collapsed">
<TextBlock x:Name="SidebarModeBadgeIcon" Text="&#xE8BD;"
@@ -648,6 +681,21 @@
<Border Grid.Row="0" Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource SeparatorColor}" BorderThickness="0,0,0,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="46,0,0,0">
<TextBlock x:Name="CurrentTabTitle"
Text="AX Agent"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
</StackPanel>
<!-- 좌: 사이드바 토글 -->
<Button x:Name="BtnToggleSidebar" Style="{StaticResource GhostBtn}"
HorizontalAlignment="Left" VerticalAlignment="Center" Margin="8,0"
@@ -658,9 +706,10 @@
</Button>
<!-- 중앙: 탭 메뉴 -->
<Border HorizontalAlignment="Center" VerticalAlignment="Center"
<Border Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Center"
Background="{DynamicResource HintBackground}"
CornerRadius="10" Padding="3"
CornerRadius="12" Padding="4"
WindowChrome.IsHitTestVisibleInChrome="True">
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="TabChat" Content="Chat" Style="{StaticResource TopTabBtn}"
@@ -676,7 +725,8 @@
</Border>
<!-- 우: 최소화 + 최대화 + 닫기 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
<StackPanel Grid.Column="2"
Orientation="Horizontal" HorizontalAlignment="Right"
VerticalAlignment="Center" Margin="0,0,4,0"
WindowChrome.IsHitTestVisibleInChrome="True">
<Button Style="{StaticResource GhostBtn}" Click="BtnMinimize_Click" ToolTip="최소화">
@@ -744,9 +794,10 @@
<ScrollViewer x:Name="MessageScroll" Grid.Row="3"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Background="{DynamicResource LauncherBackground}">
Background="{DynamicResource LauncherBackground}"
Padding="22,20,22,0">
<StackPanel x:Name="MessagePanel"
Margin="0,34,0,22"
Margin="0,20,0,28"
HorizontalAlignment="Stretch">
<StackPanel.RenderTransform>
<TranslateTransform/>
@@ -757,7 +808,7 @@
<!-- 빈 상태 -->
<StackPanel x:Name="EmptyState" Grid.Row="3"
HorizontalAlignment="Center" VerticalAlignment="Center"
MaxWidth="520">
MaxWidth="660">
<!-- 부유 AI 아이콘 -->
<Border x:Name="EmptyIcon" CornerRadius="24" Width="72" Height="72"
@@ -791,11 +842,11 @@
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock x:Name="EmptyStateTitle" Text="작업 유형을 선택하세요" FontSize="18" FontWeight="SemiBold"
<TextBlock x:Name="EmptyStateTitle" Text="작업 유형을 선택하세요" FontSize="22" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}" HorizontalAlignment="Center"/>
<TextBlock x:Name="EmptyStateDesc" Text="주제에 맞는 전문 프리셋이 자동 적용됩니다"
FontSize="12" Foreground="{DynamicResource SecondaryText}"
HorizontalAlignment="Center" Margin="0,8,0,24"/>
FontSize="12.5" Foreground="{DynamicResource SecondaryText}"
HorizontalAlignment="Center" Margin="0,10,0,28"/>
<!-- 대화 주제 버튼 (프리셋에서 동적 생성) -->
<WrapPanel x:Name="TopicButtonPanel" HorizontalAlignment="Center"
@@ -992,15 +1043,18 @@
</Border>
<!-- ── 입력 바 ── -->
<Border Grid.Row="4" Margin="48,0,48,20">
<Border Grid.Row="4"
Margin="28,0,28,22"
HorizontalAlignment="Center"
MaxWidth="980">
<StackPanel>
<Border x:Name="DraftPreviewCard"
Visibility="Collapsed"
Background="{DynamicResource ItemBackground}"
Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="18"
Padding="12,10,12,10"
CornerRadius="22"
Padding="14,12,14,12"
Margin="0,0,0,10">
<Border.Effect>
<DropShadowEffect BlurRadius="14" ShadowDepth="0" Opacity="0.06"/>
@@ -1056,7 +1110,7 @@
<StackPanel x:Name="DraftQueuePanel"
Visibility="Collapsed"
Margin="0,0,0,10"/>
<Border x:Name="InputGlowBorder" CornerRadius="26" Opacity="0"
<Border x:Name="InputGlowBorder" CornerRadius="30" Opacity="0"
Margin="-3" IsHitTestVisible="False">
<Border.BorderBrush>
<LinearGradientBrush x:Name="RainbowBrush" StartPoint="0,0" EndPoint="1,1">
@@ -1078,11 +1132,11 @@
</Border>
<!-- 실제 입력 영역 -->
<Border x:Name="InputBorder"
Background="{DynamicResource ItemBackground}"
CornerRadius="22" Padding="6"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1">
Background="{DynamicResource LauncherBackground}"
CornerRadius="28" Padding="8"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1.2">
<Border.Effect>
<DropShadowEffect BlurRadius="18" ShadowDepth="0" Opacity="0.08"/>
<DropShadowEffect BlurRadius="22" ShadowDepth="0" Opacity="0.10"/>
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
@@ -1093,7 +1147,7 @@
</Grid.RowDefinitions>
<!-- Row 0: 모델 셀렉터 + 템플릿 버튼 -->
<Grid Grid.Row="0" Margin="5,1,5,2">
<Grid Grid.Row="0" Margin="8,4,8,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
@@ -1104,8 +1158,8 @@
Grid.Column="0"
HorizontalAlignment="Left"
Margin="0"
MinHeight="26"
Padding="9,2.5"
MinHeight="30"
Padding="11,4"
Click="BtnModelSelector_Click"
ToolTip="모델/추론 빠른 설정"
WindowChrome.IsHitTestVisibleInChrome="True">
@@ -1128,9 +1182,9 @@
Grid.Column="2"
HorizontalAlignment="Right"
Margin="0"
MinHeight="26"
MinHeight="30"
MinWidth="60"
Padding="7,2.5"
Padding="10,4"
Click="BtnTemplateSelector_Click"
ToolTip="프롬프트 템플릿"
WindowChrome.IsHitTestVisibleInChrome="True">
@@ -1151,9 +1205,9 @@
<!-- Row 1: 통합 설정 패널 -->
<Border x:Name="InlineSettingsPanel"
Grid.Row="1"
Margin="8,2,8,3"
Padding="9,8,9,7"
CornerRadius="12"
Margin="8,2,8,6"
Padding="11,10,11,9"
CornerRadius="14"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Background="{DynamicResource SecondaryBackground}"
@@ -1275,7 +1329,7 @@
</ItemsControl>
<!-- 입력 영역 (Row 3) -->
<Grid Grid.Row="3">
<Grid Grid.Row="3" Margin="2,0,2,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
@@ -1290,7 +1344,7 @@
Foreground="{DynamicResource PrimaryText}"
Background="Transparent"
CaretBrush="{DynamicResource AccentColor}"
BorderThickness="0" Padding="12,8"
BorderThickness="0" Padding="14,10"
VerticalContentAlignment="Top"
AcceptsReturn="True"
PreviewKeyDown="InputBox_PreviewKeyDown"
@@ -1330,7 +1384,7 @@
<!-- 파일 첨부 -->
<Button x:Name="BtnAttach" Style="{StaticResource GhostBtn}" Grid.Column="1"
Width="34" Height="34" Margin="0,0,2,0" VerticalAlignment="Bottom"
Width="36" Height="36" Margin="0,0,4,0" VerticalAlignment="Bottom"
Click="BtnAttach_Click" ToolTip="파일 첨부"
WindowChrome.IsHitTestVisibleInChrome="True"
AllowDrop="True">
@@ -1340,7 +1394,7 @@
<!-- 내보내기 -->
<Button Style="{StaticResource GhostBtn}" Grid.Column="2"
Width="34" Height="34" Margin="0,0,2,0" VerticalAlignment="Bottom"
Width="36" Height="36" Margin="0,0,4,0" VerticalAlignment="Bottom"
Click="BtnExport_Click" ToolTip="대화 내보내기"
WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Text="&#xEDE1;" FontFamily="Segoe MDL2 Assets" FontSize="14"
@@ -1349,7 +1403,7 @@
<!-- 일시정지 -->
<Border x:Name="BtnPause" Grid.Column="3"
Width="32" Height="32" Margin="0,0,2,0"
Width="34" Height="34" Margin="0,0,4,0"
CornerRadius="8" Cursor="Hand"
Background="{DynamicResource ItemHoverBackground}" Visibility="Collapsed"
VerticalAlignment="Bottom"
@@ -1363,7 +1417,7 @@
<!-- 중지/전송 -->
<Button x:Name="BtnStop" Grid.Column="3"
Width="38" Height="38" Margin="4,0,4,0"
Width="40" Height="40" Margin="4,0,4,0"
Cursor="Hand" Click="BtnStop_Click"
VerticalAlignment="Bottom" Visibility="Collapsed"
WindowChrome.IsHitTestVisibleInChrome="True">
@@ -1378,7 +1432,7 @@
</Button.Template>
</Button>
<Button x:Name="BtnSend" Grid.Column="4"
Width="38" Height="38" Margin="4,0,4,0"
Width="42" Height="42" Margin="6,0,4,0"
Cursor="Hand" Click="BtnSend_Click"
VerticalAlignment="Bottom">
<Button.Template>
@@ -1657,6 +1711,507 @@
</Border>
</Grid>
<!-- ══ AX Agent 설정 오버레이 (채팅 화면 내부 전체 덮기) ══ -->
<Border x:Name="AgentSettingsOverlay"
Grid.Column="2"
Grid.ColumnSpan="3"
Background="{DynamicResource LauncherBackground}"
Visibility="Collapsed"
Panel.ZIndex="120">
<Grid Margin="0">
<Border Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="0"
Padding="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.RowSpan="2"
Grid.Column="0"
Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="0,0,1,0">
<StackPanel Margin="24,26,20,18">
<TextBlock Text="AX Agent 설정"
FontSize="26"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"
Margin="0,0,0,8"/>
<TextBlock Text="핵심 동작과 연결 설정을 이 화면에서 바로 관리합니다."
FontSize="12.5"
TextWrapping="Wrap"
Foreground="{DynamicResource SecondaryText}"
Margin="0,0,0,20"/>
<TextBlock Text="환경"
FontSize="11"
FontWeight="SemiBold"
Foreground="{DynamicResource SecondaryText}"
Margin="6,0,0,10"/>
<RadioButton x:Name="OverlayNavCommon"
Content="일반"
GroupName="OverlayNav"
Style="{StaticResource SettingsNavBtn}"
HorizontalAlignment="Stretch"
Checked="OverlayNav_Checked"
Tag="common"
IsChecked="True"/>
<RadioButton x:Name="OverlayNavService"
Content="서비스와 모델"
GroupName="OverlayNav"
Style="{StaticResource SettingsNavBtn}"
HorizontalAlignment="Stretch"
Margin="0,6,0,0"
Checked="OverlayNav_Checked"
Tag="service"/>
<RadioButton x:Name="OverlayNavPermission"
Content="권한과 실행"
GroupName="OverlayNav"
Style="{StaticResource SettingsNavBtn}"
HorizontalAlignment="Stretch"
Margin="0,6,0,0"
Checked="OverlayNav_Checked"
Tag="permission"/>
<RadioButton x:Name="OverlayNavAdvanced"
Content="고급"
GroupName="OverlayNav"
Style="{StaticResource SettingsNavBtn}"
HorizontalAlignment="Stretch"
Margin="0,6,0,0"
Checked="OverlayNav_Checked"
Tag="advanced"/>
</StackPanel>
</Border>
<Grid Grid.Row="0" Grid.Column="1" Margin="16,14,16,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Style="{StaticResource GhostBtn}"
Padding="10,6"
Click="BtnOverlaySettingsClose_Click"
ToolTip="대화로 돌아가기">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE72B;"
FontFamily="Segoe MDL2 Assets"
FontSize="12"
Foreground="{DynamicResource SecondaryText}"
Margin="0,0,6,0"/>
<TextBlock Text="설정"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
</StackPanel>
</Button>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="16,0,0,0">
<TextBlock Text="AX Agent 설정"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="채팅을 벗어나지 않고 AX Agent 전용 설정을 정리합니다"
Margin="0,6,0,0"
FontSize="12.5"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<Button Grid.Column="2"
Style="{StaticResource GhostBtn}"
Padding="8,6"
Click="BtnOverlaySettingsClose_Click"
ToolTip="설정 닫기">
<TextBlock Text="&#xE711;"
FontFamily="Segoe MDL2 Assets"
FontSize="12"
Foreground="{DynamicResource SecondaryText}"/>
</Button>
</Grid>
<ScrollViewer Grid.Row="1" Grid.Column="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Padding="16,4,16,16">
<StackPanel>
<Border x:Name="OverlaySectionService"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="12"
Padding="12"
Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="사용 서비스"
FontSize="12"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="대화에 사용할 LLM 서비스와 모델을 선택합니다."
Margin="0,4,0,10"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
<WrapPanel Margin="0,0,0,8">
<Border x:Name="OverlaySvcOllamaCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="OverlaySvcOllamaCard_MouseLeftButtonUp">
<TextBlock Text="Ollama" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="OverlaySvcVllmCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="OverlaySvcVllmCard_MouseLeftButtonUp">
<TextBlock Text="vLLM" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="OverlaySvcGeminiCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="OverlaySvcGeminiCard_MouseLeftButtonUp">
<TextBlock Text="Gemini" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="OverlaySvcClaudeCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="OverlaySvcClaudeCard_MouseLeftButtonUp">
<TextBlock Text="Claude" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
</WrapPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,6,0">
<TextBlock Text="서비스"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"
Margin="0,0,0,6"/>
<ComboBox x:Name="CmbOverlayService"
Height="30"
SelectionChanged="CmbOverlayService_SelectionChanged"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="6,0,0,0">
<TextBlock Text="모델"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"
Margin="0,0,0,6"/>
<ComboBox x:Name="CmbOverlayModel"
Height="30"
SelectionChanged="CmbOverlayModel_SelectionChanged"/>
</StackPanel>
</Grid>
<Grid x:Name="OverlayServiceDetailPanel" Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,6,0">
<TextBlock x:Name="OverlayEndpointLabel"
Text="기본 서버 주소"
FontSize="11.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="OverlayEndpointHint"
Text="선택한 서비스의 기본 연결 주소를 입력합니다."
Margin="0,4,0,6"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
<TextBox x:Name="TxtOverlayServiceEndpoint"
Padding="9,7"
Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="6,0,0,0">
<TextBlock x:Name="OverlayApiKeyLabel"
Text="API 키"
FontSize="11.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="OverlayApiKeyHint"
Text="접근 제어가 필요한 경우에만 입력합니다."
Margin="0,4,0,6"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
<PasswordBox x:Name="TxtOverlayServiceApiKey"
Padding="9,7"
Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
FontSize="12"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<Border x:Name="OverlaySectionQuick"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="12"
Padding="12">
<StackPanel>
<TextBlock Text="빠른 제어"
FontSize="12"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="Codex/Claude 스타일로 핵심 기능만 간결하게 제공합니다."
Margin="0,4,0,10"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
<WrapPanel>
<Button x:Name="BtnOverlayFastMode"
Style="{StaticResource OutlineHoverBtn}"
Margin="0,0,8,8"
Padding="11,6"
Content="Fast"
Click="BtnInlineFastMode_Click"/>
<Button x:Name="BtnOverlayReasoning"
Style="{StaticResource OutlineHoverBtn}"
Margin="0,0,8,8"
Padding="11,6"
Content="Reasoning"
Click="BtnInlineReasoning_Click"/>
<Button x:Name="BtnOverlayPlanMode"
Style="{StaticResource OutlineHoverBtn}"
Margin="0,0,8,8"
Padding="11,6"
Content="Plan"
Click="BtnInlinePlanMode_Click"/>
<Button x:Name="BtnOverlayPermission"
Style="{StaticResource OutlineHoverBtn}"
Margin="0,0,8,8"
Padding="11,6"
Content="권한"
Click="BtnInlinePermission_Click"/>
</WrapPanel>
</StackPanel>
</Border>
<Border x:Name="OverlaySectionDetail"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="12"
Padding="12"
Margin="0,10,0,0">
<StackPanel>
<TextBlock x:Name="OverlayAnchorCommon"
Text="상세 설정"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="트레이의 AX Agent 설정 항목을 이 화면으로 통합합니다."
Margin="0,4,0,10"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
<Grid x:Name="OverlayAiEnabledRow" Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="AX Agent 사용"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="비활성화하면 AX Agent 대화 기능이 숨겨집니다."
Margin="0,4,0,0"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<CheckBox x:Name="ChkOverlayAiEnabled"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource ToggleSwitch}"/>
</Grid>
<WrapPanel x:Name="OverlayThemePanel" Margin="0,0,0,8">
<Border x:Name="OverlayThemeSystemCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="OverlayThemeSystemCard_MouseLeftButtonUp">
<TextBlock Text="System" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="OverlayThemeLightCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="OverlayThemeLightCard_MouseLeftButtonUp">
<TextBlock Text="Light" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="OverlayThemeDarkCard"
Cursor="Hand"
CornerRadius="10"
BorderThickness="1"
BorderBrush="{DynamicResource BorderColor}"
Padding="10,8"
Margin="0,0,8,8"
MouseLeftButtonUp="OverlayThemeDarkCard_MouseLeftButtonUp">
<TextBlock Text="Dark" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
</Border>
</WrapPanel>
<StackPanel x:Name="OverlayModelEditorPanel">
<TextBlock Text="등록 모델"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"
Margin="0,4,0,6"/>
<TextBox x:Name="TxtOverlayModelInput"
Padding="8,6"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
<WrapPanel x:Name="OverlayModelChipPanel" Margin="0,10,0,0"/>
</StackPanel>
<Grid x:Name="OverlayAnchorPermission" Margin="12,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="운영 모드"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Button x:Name="BtnOverlayOperationMode"
Grid.Column="1"
MinWidth="140"
Style="{StaticResource OutlineHoverBtn}"
Click="BtnOverlayOperationMode_Click"/>
</Grid>
<Grid x:Name="OverlayFolderDataUsageRow" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="폴더 데이터 활용"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<Button x:Name="BtnOverlayFolderDataUsage"
Grid.Column="1"
MinWidth="140"
Style="{StaticResource OutlineHoverBtn}"
Click="BtnOverlayFolderDataUsage_Click"/>
</Grid>
<Grid x:Name="OverlayTlsRow" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="vLLM SSL 인증서 검증 우회 허용"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<CheckBox x:Name="ChkOverlayVllmAllowInsecureTls"
Grid.Column="1"
VerticalAlignment="Center"/>
</Grid>
<Grid x:Name="OverlayAnchorAdvanced" Margin="0,14,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="압축 시작 한도(%)"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<TextBox x:Name="TxtOverlayContextCompactTriggerPercent"
Grid.Column="1"
Padding="8,5"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</Grid>
<Grid x:Name="OverlayMaxContextTokensRow" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="최대 컨텍스트 토큰"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<TextBox x:Name="TxtOverlayMaxContextTokens"
Grid.Column="1"
Padding="8,5"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</Grid>
<Grid x:Name="OverlayMaxRetryRow" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="오류 재시도 횟수"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
<TextBox x:Name="TxtOverlayMaxRetryOnError"
Grid.Column="1"
Padding="8,5"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Foreground="{DynamicResource PrimaryText}"
FontSize="12"/>
</Grid>
<WrapPanel x:Name="OverlayAdvancedTogglePanel" Margin="0,14,0,0">
<CheckBox x:Name="ChkOverlayEnableProactiveCompact" Content="자동 컨텍스트 압축" Margin="0,0,14,8"/>
<CheckBox x:Name="ChkOverlayEnableSkillSystem" Content="스킬 시스템" Margin="0,0,14,8"/>
<CheckBox x:Name="ChkOverlayEnableToolHooks" Content="도구 훅 사용" Margin="0,0,14,8"/>
<CheckBox x:Name="ChkOverlayEnableHookInputMutation" Content="입력 변형 반영" Margin="0,0,14,8"/>
<CheckBox x:Name="ChkOverlayEnableHookPermissionUpdate" Content="권한 갱신 반영" Margin="0,0,14,8"/>
<CheckBox x:Name="ChkOverlayEnableCoworkVerification" Content="Cowork 검증" Margin="0,0,14,8"/>
<CheckBox x:Name="ChkOverlayEnableCodeVerification" Content="Code 검증" Margin="0,0,14,8"/>
<CheckBox x:Name="ChkOverlayEnableParallelTools" Content="병렬 도구 실행" Margin="0,0,14,8"/>
</WrapPanel>
</StackPanel>
</Border>
<Border Background="{DynamicResource LauncherBackground}"
Margin="0,12,0,0"
Padding="0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="저장"
Background="{DynamicResource AccentColor}"
Foreground="White"
BorderThickness="0"
Padding="12,6"
Cursor="Hand"
Click="BtnOverlaySave_Click"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</Border>
</Grid>
</Border>
<!-- ══ 미리보기 스플리터 (Column 3) ══ -->
<GridSplitter x:Name="PreviewSplitter" Grid.Column="3" Width="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

View File

@@ -15,6 +15,7 @@ namespace AxCopilot.Views;
/// <summary>AX Agent 창. 데스크톱 코파일럿 스타일 — 사이드바 + 카테고리 분류 + 타임라인.</summary>
public partial class ChatWindow : Window
{
private const string UnifiedAdminPassword = "axgo123!";
private readonly SettingsService _settings;
private readonly ChatStorageService _storage;
private readonly DraftQueueProcessorService _draftQueueProcessor = new();
@@ -76,7 +77,7 @@ public partial class ChatWindow : Window
private readonly DispatcherTimer _typingTimer;
private int _displayedLength; // 현재 화면에 표시된 글자 수
private ResourceDictionary? _agentThemeDictionary;
private AgentSettingsWindow? _agentSettingsWindow;
private bool _isOverlaySettingsSyncing;
private sealed class ConversationMeta
{
@@ -984,6 +985,15 @@ public partial class ChatWindow : Window
ApplyAgentThemeResources();
ApplyExpressionLevelUi();
ApplySidebarStateForActiveTab(animated: false);
if (CurrentTabTitle != null)
{
CurrentTabTitle.Text = _activeTab switch
{
"Cowork" => "AX Agent · Cowork",
"Code" => "AX Agent · Code",
_ => "AX Agent · Chat",
};
}
// 폴더 바는 Cowork/Code 탭에서만 표시
if (FolderBar != null)
@@ -1892,9 +1902,9 @@ public partial class ChatWindow : Window
return button;
}
actionRow.Children.Add(CreateActionButton("활용하지 않음", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
actionRow.Children.Add(CreateActionButton("소극 활용", "#EEF2FF", "#1D4ED8", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Default)));
actionRow.Children.Add(CreateActionButton("적극 활용", "#ECFDF5", "#166534", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.AcceptEdits)));
actionRow.Children.Add(CreateActionButton("읽기 전용", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
actionRow.Children.Add(CreateActionButton("권한 요청", "#EEF2FF", "#1D4ED8", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Default)));
actionRow.Children.Add(CreateActionButton("편집 자동 승인", "#ECFDF5", "#166534", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.AcceptEdits)));
actionRow.Children.Add(CreateActionButton("예외 해제", "#F3F4F6", "#374151", () => SetToolPermissionOverride(latestDenied.ToolName!, null)));
deniedStack.Children.Add(actionRow);
}
@@ -2117,7 +2127,6 @@ public partial class ChatWindow : Window
BtnPermission_Click(this, new RoutedEventArgs());
}
private bool _permissionTopBannerDismissed; // 상단 권한 배너 닫힘 상태
private string _lastPermissionBannerMode = "";
private bool GetPermissionPopupSectionExpanded(string sectionKey, bool defaultValue = false)
@@ -2137,7 +2146,6 @@ public partial class ChatWindow : Window
private void BtnPermissionTopBannerClose_Click(object sender, RoutedEventArgs e)
{
_permissionTopBannerDismissed = true;
if (PermissionTopBanner != null)
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
@@ -2167,7 +2175,6 @@ public partial class ChatWindow : Window
if (!string.Equals(_lastPermissionBannerMode, perm, StringComparison.OrdinalIgnoreCase))
{
_permissionTopBannerDismissed = false;
_lastPermissionBannerMode = perm;
}
@@ -2182,10 +2189,10 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC");
PermissionTopBannerIcon.Text = "\uE73E";
PermissionTopBannerIcon.Foreground = activeColor;
PermissionTopBannerTitle.Text = "현재 권한 모드 · 적극 활용";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 편집 자동 승인";
PermissionTopBannerTitle.Foreground = BrushFromHex("#166534");
PermissionTopBannerText.Text = "파일 편집 도구는 자동 승인하고, 명령 실행은 계속 확인합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "모든 파일 편집 자동 승인합니다. 명령 실행은 계속 확인합니다.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else if (perm == PermissionModeCatalog.Deny)
@@ -2198,10 +2205,10 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC");
PermissionTopBannerIcon.Text = "\uE73E";
PermissionTopBannerIcon.Foreground = denyColor;
PermissionTopBannerTitle.Text = "현재 권한 모드 · 활용하지 않음";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 읽기 전용";
PermissionTopBannerTitle.Foreground = denyColor;
PermissionTopBannerText.Text = "파일 읽기만 허용하고 생성/수정/삭제는 차단합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else if (perm == PermissionModeCatalog.BypassPermissions)
@@ -2214,10 +2221,10 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#FDBA74");
PermissionTopBannerIcon.Text = "\uE814";
PermissionTopBannerIcon.Foreground = autoColor;
PermissionTopBannerTitle.Text = "현재 권한 모드 · 완전 자동";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 건너뛰기";
PermissionTopBannerTitle.Foreground = autoColor;
PermissionTopBannerText.Text = "권한 확인을 대부분 생략합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "모든 권한을 허용합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else if (perm == PermissionModeCatalog.DontAsk)
@@ -2233,7 +2240,7 @@ public partial class ChatWindow : Window
PermissionTopBannerTitle.Text = "현재 권한 모드 · 질문 없이 진행";
PermissionTopBannerTitle.Foreground = dangerColor;
PermissionTopBannerText.Text = "권한 확인을 거의 생략합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else
@@ -2253,20 +2260,20 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#C7D2FE");
PermissionTopBannerIcon.Text = "\uE7C3";
PermissionTopBannerIcon.Foreground = BrushFromHex("#4338CA");
PermissionTopBannerTitle.Text = "현재 권한 모드 · 계획 중심";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 계획 모드";
PermissionTopBannerTitle.Foreground = BrushFromHex("#4338CA");
PermissionTopBannerText.Text = "쓰기 작업은 제한하고, 먼저 계획과 승인 흐름을 우선합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "변경 전에 계획을 먼저 만들고 승인 흐름을 우선합니다.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
else if (perm == PermissionModeCatalog.Default)
{
PermissionTopBanner.BorderBrush = BrushFromHex("#BFDBFE");
PermissionTopBannerIcon.Text = "\uE8D7";
PermissionTopBannerIcon.Foreground = BrushFromHex("#1D4ED8");
PermissionTopBannerTitle.Text = "현재 권한 모드 · 소극 활용";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 요청";
PermissionTopBannerTitle.Foreground = BrushFromHex("#1D4ED8");
PermissionTopBannerText.Text = "변경 전 확인하고, 필요한 경우에만 파일 접근을 진행합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "변경하기 전에 항상 확인합니다.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
else
{
@@ -5667,8 +5674,20 @@ public partial class ChatWindow : Window
if (!_slashVisibleItemByAbsoluteIndex.TryGetValue(_slashPalette.SelectedIndex, out var item))
return;
var bounds = item.TransformToAncestor(SlashScrollViewer)
.TransformBounds(new Rect(0, 0, item.ActualWidth, item.ActualHeight));
if (!IsVisualDescendantOf(item, SlashScrollViewer))
return;
Rect bounds;
try
{
bounds = item.TransformToAncestor(SlashScrollViewer)
.TransformBounds(new Rect(0, 0, item.ActualWidth, item.ActualHeight));
}
catch
{
// 렌더 트리 갱신 중에는 transform이 실패할 수 있어 조용히 무시.
return;
}
if (bounds.Top < 0)
SlashScrollViewer.ScrollToVerticalOffset(SlashScrollViewer.VerticalOffset + bounds.Top - 8);
@@ -5676,6 +5695,22 @@ public partial class ChatWindow : Window
SlashScrollViewer.ScrollToVerticalOffset(SlashScrollViewer.VerticalOffset + (bounds.Bottom - SlashScrollViewer.ViewportHeight) + 8);
}
private static bool IsVisualDescendantOf(DependencyObject? child, DependencyObject? parent)
{
if (child == null || parent == null)
return false;
var current = child;
while (current != null)
{
if (ReferenceEquals(current, parent))
return true;
current = VisualTreeHelper.GetParent(current);
}
return false;
}
private void UpdateSlashSelectionVisualState()
{
if (_slashVisibleItemByAbsoluteIndex.Count == 0)
@@ -12997,66 +13032,17 @@ public partial class ChatWindow : Window
private void OpenAgentSettingsWindow()
{
if (_agentSettingsWindow != null)
RefreshOverlaySettingsPanel();
AgentSettingsOverlay.Visibility = Visibility.Visible;
InlineSettingsPanel.Visibility = Visibility.Collapsed;
SetOverlaySection("common");
Dispatcher.BeginInvoke(() =>
{
try
{
if (_agentSettingsWindow.IsVisible)
{
_agentSettingsWindow.Activate();
return;
}
}
catch
{
// ignore stale window instance
}
_agentSettingsWindow = null;
}
var win = new AgentSettingsWindow(_settings);
if (IsLoaded && IsVisible)
win.Owner = this;
_agentSettingsWindow = win;
win.Closed += (_, _) => _agentSettingsWindow = null;
try
{
win.Resources.MergedDictionaries.Insert(0, new ResourceDictionary
{
Source = BuildAgentThemeDictionaryUri(),
});
}
catch
{
// 테마 사전 로드 실패 시에도 설정창 자체는 열리도록 유지
}
bool changed;
try
{
changed = win.ShowDialog() == true;
}
catch
{
// 모달 창 오픈에 실패하면 일반 창으로라도 설정 접근을 보장
try
{
win.Show();
win.Activate();
}
catch { }
return;
}
if (!changed)
return;
_appState.LoadFromSettings(_settings);
ApplyAgentThemeResources();
UpdateSidebarModeMenu();
UpdateModelLabel();
RefreshInlineSettingsPanel();
UpdateTabUI();
ShowToast("AX Agent 설정이 저장되었습니다.");
if (CmbOverlayModel.Items.Count > 0)
CmbOverlayModel.Focus();
else
InputBox.Focus();
}, DispatcherPriority.Input);
}
public void OpenAgentSettingsFromExternal()
@@ -13069,6 +13055,515 @@ public partial class ChatWindow : Window
}, DispatcherPriority.Input);
}
private void BtnOverlaySettingsClose_Click(object sender, RoutedEventArgs e)
{
AgentSettingsOverlay.Visibility = Visibility.Collapsed;
InputBox.Focus();
}
private void OverlayNav_Checked(object sender, RoutedEventArgs e)
{
if (sender is not RadioButton rb || rb.Tag is not string tag)
return;
SetOverlaySection(tag);
}
private void SetOverlaySection(string tag)
{
if (OverlaySectionService == null || OverlaySectionQuick == null || OverlaySectionDetail == null)
return;
var section = string.IsNullOrWhiteSpace(tag) ? "common" : tag.Trim().ToLowerInvariant();
OverlaySectionService.Visibility = section == "service" ? Visibility.Visible : Visibility.Collapsed;
OverlaySectionQuick.Visibility = section == "permission" ? Visibility.Visible : Visibility.Collapsed;
OverlaySectionDetail.Visibility = section == "service" ? Visibility.Collapsed : Visibility.Visible;
var showCommon = section == "common";
var showPermission = section == "permission";
var showAdvanced = section == "advanced";
if (OverlayAiEnabledRow != null)
OverlayAiEnabledRow.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
if (OverlayThemePanel != null)
OverlayThemePanel.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
if (OverlayModelEditorPanel != null)
OverlayModelEditorPanel.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAnchorPermission != null)
OverlayAnchorPermission.Visibility = showPermission ? Visibility.Visible : Visibility.Collapsed;
if (OverlayFolderDataUsageRow != null)
OverlayFolderDataUsageRow.Visibility = showCommon || showPermission ? Visibility.Visible : Visibility.Collapsed;
if (OverlayTlsRow != null)
OverlayTlsRow.Visibility = showCommon || showPermission ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAnchorAdvanced != null)
OverlayAnchorAdvanced.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
if (OverlayMaxContextTokensRow != null)
OverlayMaxContextTokensRow.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
if (OverlayMaxRetryRow != null)
OverlayMaxRetryRow.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAdvancedTogglePanel != null)
OverlayAdvancedTogglePanel.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
}
private void RefreshOverlaySettingsPanel()
{
if (CmbOverlayService == null || CmbOverlayModel == null)
return;
var llm = _settings.Settings.Llm;
var service = (llm.Service ?? "ollama").ToLowerInvariant();
var models = GetModelCandidates(service);
_isOverlaySettingsSyncing = true;
try
{
CmbOverlayService.Items.Clear();
foreach (var svc in new[] { "ollama", "vllm", "gemini", "claude" })
{
CmbOverlayService.Items.Add(new ComboBoxItem
{
Content = ServiceLabel(svc),
Tag = svc
});
}
CmbOverlayService.SelectedItem = CmbOverlayService.Items
.OfType<ComboBoxItem>()
.FirstOrDefault(i => string.Equals(i.Tag as string, service, StringComparison.OrdinalIgnoreCase));
CmbOverlayModel.Items.Clear();
foreach (var model in models)
{
CmbOverlayModel.Items.Add(new ComboBoxItem
{
Content = model.Label,
Tag = model.Id
});
}
CmbOverlayModel.SelectedItem = CmbOverlayModel.Items
.OfType<ComboBoxItem>()
.FirstOrDefault(i => string.Equals(i.Tag as string, llm.Model, StringComparison.OrdinalIgnoreCase));
if (CmbOverlayModel.SelectedItem == null && CmbOverlayModel.Items.Count > 0)
CmbOverlayModel.SelectedIndex = 0;
if (TxtOverlayModelInput != null)
TxtOverlayModelInput.Text = llm.Model ?? "";
if (ChkOverlayAiEnabled != null)
ChkOverlayAiEnabled.IsChecked = _settings.Settings.AiEnabled;
if (TxtOverlayServiceEndpoint != null)
TxtOverlayServiceEndpoint.Text = GetOverlayServiceEndpoint(service);
if (TxtOverlayServiceApiKey != null)
TxtOverlayServiceApiKey.Password = GetOverlayServiceApiKey(service);
if (ChkOverlayVllmAllowInsecureTls != null)
ChkOverlayVllmAllowInsecureTls.IsChecked = llm.VllmAllowInsecureTls;
if (TxtOverlayContextCompactTriggerPercent != null)
TxtOverlayContextCompactTriggerPercent.Text = Math.Clamp(llm.ContextCompactTriggerPercent, 10, 95).ToString();
if (TxtOverlayMaxContextTokens != null)
TxtOverlayMaxContextTokens.Text = Math.Max(1024, llm.MaxContextTokens).ToString();
if (TxtOverlayMaxRetryOnError != null)
TxtOverlayMaxRetryOnError.Text = Math.Clamp(llm.MaxRetryOnError, 0, 10).ToString();
if (ChkOverlayEnableProactiveCompact != null)
ChkOverlayEnableProactiveCompact.IsChecked = llm.EnableProactiveContextCompact;
if (ChkOverlayEnableSkillSystem != null)
ChkOverlayEnableSkillSystem.IsChecked = llm.EnableSkillSystem;
if (ChkOverlayEnableToolHooks != null)
ChkOverlayEnableToolHooks.IsChecked = llm.EnableToolHooks;
if (ChkOverlayEnableHookInputMutation != null)
ChkOverlayEnableHookInputMutation.IsChecked = llm.EnableHookInputMutation;
if (ChkOverlayEnableHookPermissionUpdate != null)
ChkOverlayEnableHookPermissionUpdate.IsChecked = llm.EnableHookPermissionUpdate;
if (ChkOverlayEnableCoworkVerification != null)
ChkOverlayEnableCoworkVerification.IsChecked = llm.EnableCoworkVerification;
if (ChkOverlayEnableCodeVerification != null)
ChkOverlayEnableCodeVerification.IsChecked = llm.Code.EnableCodeVerification;
if (ChkOverlayEnableParallelTools != null)
ChkOverlayEnableParallelTools.IsChecked = llm.EnableParallelTools;
RefreshOverlayThemeCards();
RefreshOverlayServiceCards();
RefreshOverlayModeButtons();
RefreshOverlayServiceFieldLabels(service);
BuildOverlayModelChips(service);
}
finally
{
_isOverlaySettingsSyncing = false;
}
}
private void CmbOverlayService_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isOverlaySettingsSyncing || CmbOverlayService.SelectedItem is not ComboBoxItem serviceItem || serviceItem.Tag is not string service)
return;
var llm = _settings.Settings.Llm;
llm.Service = service;
var candidates = GetModelCandidates(service);
if (candidates.Count > 0 && !candidates.Any(m => m.Id == llm.Model))
llm.Model = candidates[0].Id;
_settings.Save();
_appState.LoadFromSettings(_settings);
UpdateModelLabel();
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void CmbOverlayModel_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isOverlaySettingsSyncing || CmbOverlayModel.SelectedItem is not ComboBoxItem modelItem || modelItem.Tag is not string modelId)
return;
_settings.Settings.Llm.Model = modelId;
_settings.Save();
_appState.LoadFromSettings(_settings);
UpdateModelLabel();
RefreshInlineSettingsPanel();
}
private void RefreshOverlayThemeCards()
{
var selected = (_settings.Settings.Llm.AgentTheme ?? "system").ToLowerInvariant();
SetOverlayCardSelection(OverlayThemeSystemCard, selected == "system");
SetOverlayCardSelection(OverlayThemeLightCard, selected == "light");
SetOverlayCardSelection(OverlayThemeDarkCard, selected == "dark");
}
private void RefreshOverlayServiceCards()
{
var service = (_settings.Settings.Llm.Service ?? "ollama").ToLowerInvariant();
SetOverlayCardSelection(OverlaySvcOllamaCard, service == "ollama");
SetOverlayCardSelection(OverlaySvcVllmCard, service == "vllm");
SetOverlayCardSelection(OverlaySvcGeminiCard, service == "gemini");
SetOverlayCardSelection(OverlaySvcClaudeCard, service is "claude" or "sigmoid");
}
private void RefreshOverlayModeButtons()
{
var llm = _settings.Settings.Llm;
BtnOverlayOperationMode.Content = OperationModePolicy.Normalize(_settings.Settings.OperationMode) == OperationModePolicy.ExternalMode
? "사외 모드"
: "사내 모드";
BtnOverlayFolderDataUsage.Content = _folderDataUsage switch
{
"active" => "적극 활용",
"passive" => "소극 활용",
_ => "활용하지 않음",
};
BtnOverlayPermission.Content = PermissionModeCatalog.ToDisplayLabel(PermissionModeCatalog.NormalizeGlobalMode(llm.FilePermission));
BtnOverlayPlanMode.Content = PlanModeLabel(llm.PlanMode);
BtnOverlayReasoning.Content = ReasoningLabel(llm.AgentDecisionLevel);
}
private void RefreshOverlayServiceFieldLabels(string service)
{
if (OverlayEndpointLabel == null || OverlayEndpointHint == null || OverlayApiKeyLabel == null || OverlayApiKeyHint == null)
return;
switch (service)
{
case "ollama":
OverlayEndpointLabel.Text = "Ollama 서버 주소";
OverlayEndpointHint.Text = "사내 로컬 Ollama 기본 주소를 입력합니다.";
OverlayApiKeyLabel.Text = "Ollama API 키";
OverlayApiKeyHint.Text = "사내 게이트웨이를 쓰는 경우에만 입력합니다.";
break;
case "vllm":
OverlayEndpointLabel.Text = "vLLM 서버 주소";
OverlayEndpointHint.Text = "OpenAI 호환 엔드포인트 주소를 입력합니다.";
OverlayApiKeyLabel.Text = "vLLM API 키";
OverlayApiKeyHint.Text = "사내 인증 게이트웨이를 쓰는 경우에만 입력합니다.";
break;
case "gemini":
OverlayEndpointLabel.Text = "기본 서버 주소";
OverlayEndpointHint.Text = "Gemini는 기본 주소를 사용합니다. 비워두면 기본값을 사용합니다.";
OverlayApiKeyLabel.Text = "Gemini API 키";
OverlayApiKeyHint.Text = "외부 호출에 필요한 키를 입력합니다.";
break;
default:
OverlayEndpointLabel.Text = "기본 서버 주소";
OverlayEndpointHint.Text = "Claude는 기본 주소를 사용합니다. 비워두면 기본값을 사용합니다.";
OverlayApiKeyLabel.Text = "Claude API 키";
OverlayApiKeyHint.Text = "외부 호출에 필요한 키를 입력합니다.";
break;
}
}
private string GetOverlayServiceEndpoint(string service)
{
var llm = _settings.Settings.Llm;
return service switch
{
"ollama" => llm.OllamaEndpoint ?? "",
"vllm" => llm.VllmEndpoint ?? "",
"gemini" => llm.Endpoint ?? "",
"claude" or "sigmoid" => llm.Endpoint ?? "",
_ => llm.Endpoint ?? ""
};
}
private string GetOverlayServiceApiKey(string service)
{
var llm = _settings.Settings.Llm;
return service switch
{
"ollama" => llm.OllamaApiKey ?? "",
"vllm" => llm.VllmApiKey ?? "",
"gemini" => llm.GeminiApiKey ?? "",
"claude" or "sigmoid" => llm.ClaudeApiKey ?? "",
_ => llm.ApiKey ?? ""
};
}
private void BuildOverlayModelChips(string service)
{
if (OverlayModelChipPanel == null)
return;
OverlayModelChipPanel.Children.Clear();
foreach (var model in GetModelCandidates(service))
{
var captured = model.Id;
var border = new Border
{
Cursor = Cursors.Hand,
CornerRadius = new CornerRadius(10),
BorderThickness = new Thickness(1),
BorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
Background = Brushes.Transparent,
Padding = new Thickness(10, 6, 10, 6),
Margin = new Thickness(0, 0, 8, 8),
Child = new TextBlock
{
Text = model.Label,
FontSize = 11,
Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.Black,
}
};
border.MouseLeftButtonUp += (_, _) =>
{
if (TxtOverlayModelInput != null)
TxtOverlayModelInput.Text = captured;
};
OverlayModelChipPanel.Children.Add(border);
}
}
private void SetOverlayCardSelection(Border border, bool selected)
{
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
var normal = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
border.BorderBrush = selected ? accent : normal;
border.Background = selected
? (TryFindResource("HintBackground") as Brush ?? Brushes.Transparent)
: Brushes.Transparent;
}
private void OverlayThemeSystemCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_settings.Settings.Llm.AgentTheme = "system";
RefreshOverlayThemeCards();
}
private void OverlayThemeLightCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_settings.Settings.Llm.AgentTheme = "light";
RefreshOverlayThemeCards();
}
private void OverlayThemeDarkCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_settings.Settings.Llm.AgentTheme = "dark";
RefreshOverlayThemeCards();
}
private void OverlaySvcOllamaCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("ollama");
private void OverlaySvcVllmCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("vllm");
private void OverlaySvcGeminiCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("gemini");
private void OverlaySvcClaudeCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("claude");
private void SetOverlayService(string service)
{
_settings.Settings.Llm.Service = service;
RefreshOverlaySettingsPanel();
}
private void BtnOverlayOperationMode_Click(object sender, RoutedEventArgs e)
{
var next = OperationModePolicy.Normalize(_settings.Settings.OperationMode) == OperationModePolicy.ExternalMode
? OperationModePolicy.InternalMode
: OperationModePolicy.ExternalMode;
if (!PromptOverlayPasswordDialog("운영 모드 변경", "사내/사외 모드 변경", "비밀번호를 입력하세요."))
return;
_settings.Settings.OperationMode = next;
RefreshOverlayModeButtons();
}
private void BtnOverlayFolderDataUsage_Click(object sender, RoutedEventArgs e)
{
_folderDataUsage = _folderDataUsage switch
{
"none" => "passive",
"passive" => "active",
_ => "none",
};
RefreshOverlayModeButtons();
}
private void BtnOverlaySave_Click(object sender, RoutedEventArgs e)
{
var llm = _settings.Settings.Llm;
var service = (llm.Service ?? "ollama").Trim().ToLowerInvariant();
var endpoint = TxtOverlayServiceEndpoint?.Text.Trim() ?? "";
var apiKey = TxtOverlayServiceApiKey?.Password ?? "";
_settings.Settings.AiEnabled = ChkOverlayAiEnabled?.IsChecked == true;
llm.Model = TxtOverlayModelInput?.Text.Trim() ?? llm.Model;
llm.VllmAllowInsecureTls = ChkOverlayVllmAllowInsecureTls?.IsChecked == true;
llm.EnableProactiveContextCompact = ChkOverlayEnableProactiveCompact?.IsChecked == true;
llm.ContextCompactTriggerPercent = ParseOverlayInt(TxtOverlayContextCompactTriggerPercent?.Text, 80, 10, 95);
llm.MaxContextTokens = ParseOverlayInt(TxtOverlayMaxContextTokens?.Text, 4096, 1024, 200000);
llm.MaxRetryOnError = ParseOverlayInt(TxtOverlayMaxRetryOnError?.Text, 3, 0, 10);
llm.EnableSkillSystem = ChkOverlayEnableSkillSystem?.IsChecked == true;
llm.EnableToolHooks = ChkOverlayEnableToolHooks?.IsChecked == true;
llm.EnableHookInputMutation = ChkOverlayEnableHookInputMutation?.IsChecked == true;
llm.EnableHookPermissionUpdate = ChkOverlayEnableHookPermissionUpdate?.IsChecked == true;
llm.EnableCoworkVerification = ChkOverlayEnableCoworkVerification?.IsChecked == true;
llm.Code.EnableCodeVerification = ChkOverlayEnableCodeVerification?.IsChecked == true;
llm.EnableParallelTools = ChkOverlayEnableParallelTools?.IsChecked == true;
llm.FolderDataUsage = _folderDataUsage;
llm.AgentUiExpressionLevel = "rich";
switch (service)
{
case "ollama":
llm.OllamaEndpoint = endpoint;
llm.OllamaApiKey = apiKey;
llm.Endpoint = endpoint;
llm.ApiKey = apiKey;
llm.OllamaModel = llm.Model;
break;
case "vllm":
llm.VllmEndpoint = endpoint;
llm.VllmApiKey = apiKey;
llm.Endpoint = endpoint;
llm.ApiKey = apiKey;
llm.VllmModel = llm.Model;
break;
case "gemini":
llm.Endpoint = endpoint;
llm.GeminiApiKey = apiKey;
llm.ApiKey = apiKey;
llm.GeminiModel = llm.Model;
break;
case "claude":
case "sigmoid":
llm.Endpoint = endpoint;
llm.ClaudeApiKey = apiKey;
llm.ApiKey = apiKey;
llm.ClaudeModel = llm.Model;
break;
}
_settings.Save();
_appState.LoadFromSettings(_settings);
ApplyAgentThemeResources();
UpdatePermissionUI();
UpdateDataUsageUI();
SaveConversationSettings();
RefreshInlineSettingsPanel();
UpdateModelLabel();
UpdateTabUI();
AgentSettingsOverlay.Visibility = Visibility.Collapsed;
ShowToast("AX Agent 설정이 저장되었습니다.");
}
private void BtnOpenFullSettings_Click(object sender, RoutedEventArgs e)
{
if (System.Windows.Application.Current is App app)
app.OpenSettingsFromChat();
}
private bool PromptOverlayPasswordDialog(string title, string header, string message)
{
var bgBrush = TryFindResource("LauncherBackground") as Brush ?? Brushes.White;
var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.WhiteSmoke;
var dlg = new Window
{
Title = title,
Width = 340,
SizeToContent = SizeToContent.Height,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = this,
ResizeMode = ResizeMode.NoResize,
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
Background = Brushes.Transparent,
ShowInTaskbar = false,
};
var border = new Border
{
Background = bgBrush,
CornerRadius = new CornerRadius(12),
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(20),
};
var stack = new StackPanel();
stack.Children.Add(new TextBlock { Text = header, FontSize = 15, FontWeight = FontWeights.SemiBold, Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 12) });
stack.Children.Add(new TextBlock { Text = message, FontSize = 12, Foreground = subFgBrush, Margin = new Thickness(0, 0, 0, 6) });
var pwBox = new PasswordBox
{
FontSize = 14,
Padding = new Thickness(8, 6, 8, 6),
Background = itemBg,
Foreground = fgBrush,
BorderBrush = borderBrush,
PasswordChar = '*',
};
stack.Children.Add(pwBox);
var btnRow = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 16, 0, 0) };
var cancelBtn = new Button { Content = "취소", Padding = new Thickness(16, 6, 16, 6), Margin = new Thickness(0, 0, 8, 0) };
cancelBtn.Click += (_, _) => dlg.DialogResult = false;
btnRow.Children.Add(cancelBtn);
var okBtn = new Button { Content = "확인", Padding = new Thickness(16, 6, 16, 6), IsDefault = true };
okBtn.Click += (_, _) =>
{
if (pwBox.Password == UnifiedAdminPassword)
dlg.DialogResult = true;
else
{
pwBox.Clear();
pwBox.Focus();
}
};
btnRow.Children.Add(okBtn);
stack.Children.Add(btnRow);
border.Child = stack;
dlg.Content = border;
dlg.Loaded += (_, _) => pwBox.Focus();
return dlg.ShowDialog() == true;
}
private static int ParseOverlayInt(string? text, int fallback, int min, int max)
{
if (!int.TryParse(text, out var value))
value = fallback;
return Math.Clamp(value, min, max);
}
private void BtnInlineSettingsClose_Click(object sender, RoutedEventArgs e)
=> InlineSettingsPanel.Visibility = Visibility.Collapsed;
@@ -13108,6 +13603,7 @@ public partial class ChatWindow : Window
_settings.Save();
_appState.LoadFromSettings(_settings);
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlineReasoning_Click(object sender, RoutedEventArgs e)
@@ -13117,6 +13613,7 @@ public partial class ChatWindow : Window
_settings.Save();
_appState.LoadFromSettings(_settings);
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlinePlanMode_Click(object sender, RoutedEventArgs e)
@@ -13126,6 +13623,7 @@ public partial class ChatWindow : Window
_settings.Save();
_appState.LoadFromSettings(_settings);
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlinePermission_Click(object sender, RoutedEventArgs e)
@@ -13137,6 +13635,7 @@ public partial class ChatWindow : Window
UpdatePermissionUI();
SaveConversationSettings();
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlineSkill_Click(object sender, RoutedEventArgs e)

View File

@@ -3,9 +3,12 @@ using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using AxCopilot.Services;
using FormsCursor = System.Windows.Forms.Cursor;
using FormsScreen = System.Windows.Forms.Screen;
namespace AxCopilot.Views;
@@ -48,14 +51,19 @@ public partial class DockBarWindow : Window
private DispatcherTimer? _glowTimer;
/// <summary>설정 저장 콜백 (위치 저장용).</summary>
public Action<double, double>? OnPositionChanged { get; set; }
public Action<string, double, double>? OnPositionChanged { get; set; }
public DockBarWindow()
{
InitializeComponent();
MouseLeftButtonDown += (_, e) => { if (e.LeftButton == MouseButtonState.Pressed) try { DragMove(); } catch { } };
LocationChanged += (_, _) => OnPositionChanged?.Invoke(Left, Top);
LocationChanged += (_, _) =>
{
var deviceName = GetCurrentMonitorDeviceName();
if (!string.IsNullOrWhiteSpace(deviceName))
OnPositionChanged?.Invoke(deviceName, Left, Top);
};
Loaded += (_, _) => PositionDock();
Closed += (_, _) => { _timer?.Stop(); _glowTimer?.Stop(); _cpuCounter?.Dispose(); };
}
@@ -65,7 +73,7 @@ public partial class DockBarWindow : Window
{
Opacity = Math.Clamp(opacity, 0.3, 1.0);
if (left >= 0 && top >= 0)
if (left >= 0 && top >= 0 && IsWithinConnectedScreen(left, top))
{
Left = left;
Top = top;
@@ -269,11 +277,34 @@ public partial class DockBarWindow : Window
private void PositionDock()
{
var screen = SystemParameters.WorkArea;
Left = (screen.Width - ActualWidth) / 2 + screen.Left;
var screen = FormsScreen.FromPoint(FormsCursor.Position).WorkingArea;
Left = screen.Left + (screen.Width - ActualWidth) / 2;
Top = screen.Bottom - ActualHeight - 8;
}
private string GetCurrentMonitorDeviceName()
{
var handle = new WindowInteropHelper(this).Handle;
return handle != IntPtr.Zero
? FormsScreen.FromHandle(handle).DeviceName
: FormsScreen.FromPoint(FormsCursor.Position).DeviceName;
}
private bool IsWithinConnectedScreen(double left, double top)
{
var width = ActualWidth > 0 ? ActualWidth : (Width > 0 ? Width : 420);
var height = ActualHeight > 0 ? ActualHeight : (Height > 0 ? Height : 56);
foreach (var screen in FormsScreen.AllScreens)
{
var area = screen.WorkingArea;
if (left >= area.Left && top >= area.Top && left + width <= area.Right && top + height <= area.Bottom)
return true;
}
return false;
}
private void OnTick(object? sender, EventArgs e)
{
if (_clockText != null)

View File

@@ -13,6 +13,7 @@ internal sealed class ModelRegistrationDialog : Window
private readonly TextBox _modelBox;
private readonly TextBox _endpointBox;
private readonly TextBox _apiKeyBox;
private readonly CheckBox _allowInsecureTlsCheck;
// CP4D 인증 필드
private readonly ComboBox _authTypeBox;
@@ -25,13 +26,14 @@ internal sealed class ModelRegistrationDialog : Window
public string ModelName => _modelBox.Text.Trim();
public string Endpoint => _endpointBox.Text.Trim();
public string ApiKey => _apiKeyBox.Text.Trim();
public bool AllowInsecureTls => _allowInsecureTlsCheck.IsChecked == true;
public string AuthType => (_authTypeBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "bearer";
public string Cp4dUrl => _cp4dUrlBox.Text.Trim();
public string Cp4dUsername => _cp4dUsernameBox.Text.Trim();
public string Cp4dPassword => _cp4dPasswordBox.Password.Trim();
public ModelRegistrationDialog(string service, string existingAlias = "", string existingModel = "",
string existingEndpoint = "", string existingApiKey = "",
string existingEndpoint = "", string existingApiKey = "", bool existingAllowInsecureTls = false,
string existingAuthType = "bearer", string existingCp4dUrl = "",
string existingCp4dUsername = "", string existingCp4dPassword = "")
{
@@ -211,6 +213,48 @@ internal sealed class ModelRegistrationDialog : Window
};
stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _endpointBox });
var tlsPanel = new Border
{
Background = Brushes.Transparent,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(10, 8, 10, 8),
Margin = new Thickness(0, 10, 0, 0),
};
var tlsGrid = new Grid();
tlsGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
tlsGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
var tlsLeft = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
tlsLeft.Children.Add(new TextBlock
{
Text = "SSL 인증서 검증 생략",
FontSize = 12,
FontWeight = FontWeights.SemiBold,
Foreground = primaryText,
});
tlsLeft.Children.Add(new TextBlock
{
Text = "사내 자체 인증서 환경에서만 사용하세요.",
FontSize = 11,
Foreground = secondaryText,
Margin = new Thickness(0, 2, 0, 0),
});
Grid.SetColumn(tlsLeft, 0);
tlsGrid.Children.Add(tlsLeft);
_allowInsecureTlsCheck = new CheckBox
{
IsChecked = existingAllowInsecureTls,
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(12, 0, 0, 0),
};
_allowInsecureTlsCheck.Style = Application.Current.TryFindResource("ToggleSwitch") as Style ?? _allowInsecureTlsCheck.Style;
Grid.SetColumn(_allowInsecureTlsCheck, 1);
tlsGrid.Children.Add(_allowInsecureTlsCheck);
tlsPanel.Child = tlsGrid;
stack.Children.Add(tlsPanel);
// ── 인증 방식 선택 ──────────────────────────────────────────────────
stack.Children.Add(new TextBlock
{

View File

@@ -47,9 +47,12 @@ internal sealed class PlanViewerWindow : Window
private List<string> _steps = new();
private int _currentStep = -1;
private bool _isExecuting;
private readonly string _uiExpressionLevel;
public PlanViewerWindow()
{
_uiExpressionLevel = ResolveUiExpressionLevel();
Width = 640;
Height = 520;
MinWidth = 480;
@@ -156,6 +159,8 @@ internal sealed class PlanViewerWindow : Window
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(20, 6, 20, 0),
};
if (_uiExpressionLevel == "simple")
toolBar.Visibility = Visibility.Collapsed;
var expandAllBtn = MakeToolbarButton("\uE70D", "모두 열기", secondaryText, hoverBgTb);
expandAllBtn.MouseLeftButtonUp += (_, _) =>
@@ -281,6 +286,11 @@ internal sealed class PlanViewerWindow : Window
_currentStep = -1;
_isExecuting = false;
_expandedSteps.Clear(); // 새 계획 표시 시 모두 접힌 상태로 시작
if (_uiExpressionLevel == "rich")
{
for (int i = 0; i < _steps.Count; i++)
_expandedSteps.Add(i);
}
RenderSteps();
BuildApprovalButtons();
@@ -361,6 +371,56 @@ internal sealed class PlanViewerWindow : Window
var canEdit = !_isExecuting && _currentStep < 0; // 승인 대기 중에만 편집/순서변경 가능
var summaryText = _uiExpressionLevel switch
{
"simple" => _isExecuting
? "진행률을 간단히 표시합니다."
: $"{_steps.Count}단계 계획입니다. 핵심 단계만 확인하고 승인하세요.",
"rich" => _isExecuting
? "현재 단계를 기준으로 진행률을 표시합니다. 필요 시 단계를 펼쳐 세부 내용을 확인할 수 있습니다."
: $"총 {_steps.Count}단계입니다. 단계별 내용을 열어 우선순위/의존성을 검토한 뒤 승인 또는 수정 요청을 선택하세요.",
_ => _isExecuting
? "현재 단계를 기준으로 진행률을 표시합니다."
: $"총 {_steps.Count}단계입니다. 단계를 펼쳐 검토한 후 승인 또는 수정 요청을 선택하세요.",
};
_stepsPanel.Children.Add(new Border
{
Background = new SolidColorBrush(Color.FromArgb(0x12,
((SolidColorBrush)accentBrush).Color.R,
((SolidColorBrush)accentBrush).Color.G,
((SolidColorBrush)accentBrush).Color.B)),
BorderBrush = new SolidColorBrush(Color.FromArgb(0x40,
((SolidColorBrush)accentBrush).Color.R,
((SolidColorBrush)accentBrush).Color.G,
((SolidColorBrush)accentBrush).Color.B)),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(10),
Padding = new Thickness(10, 8, 10, 8),
Margin = new Thickness(0, 0, 0, 8),
Child = new StackPanel
{
Children =
{
new TextBlock
{
Text = _isExecuting ? "계획 실행 중" : "계획 승인 대기",
FontSize = 12,
FontWeight = FontWeights.SemiBold,
Foreground = accentBrush,
},
new TextBlock
{
Text = summaryText,
FontSize = 11.5,
Foreground = secondaryText,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 3, 0, 0),
}
}
}
});
for (int i = 0; i < _steps.Count; i++)
{
var step = _steps[i];
@@ -768,7 +828,11 @@ internal sealed class PlanViewerWindow : Window
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
var approveBtn = CreateActionButton("\uE73E", "승인", accentBrush, Brushes.White, true);
var approveLabel = _uiExpressionLevel == "simple" ? "승인" : "승인 후 실행";
var editLabel = _uiExpressionLevel == "simple" ? "수정" : "수정 피드백";
var rejectLabel = _uiExpressionLevel == "simple" ? "취소" : "거부";
var approveBtn = CreateActionButton("\uE73E", approveLabel, accentBrush, Brushes.White, true);
approveBtn.MouseLeftButtonUp += (_, _) =>
{
_tcs?.TrySetResult(null);
@@ -776,19 +840,12 @@ internal sealed class PlanViewerWindow : Window
};
_btnPanel.Children.Add(approveBtn);
var editBtn = CreateActionButton("\uE70F", "수정 요청", accentBrush, accentBrush, false);
var editBtn = CreateActionButton("\uE70F", editLabel, accentBrush, accentBrush, false);
editBtn.MouseLeftButtonUp += (_, _) => ShowEditInput();
_btnPanel.Children.Add(editBtn);
var reconfirmBtn = CreateActionButton("\uE72C", "재확인",
Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
reconfirmBtn.MouseLeftButtonUp += (_, _) =>
_tcs?.TrySetResult("계획을 다시 검토하고 더 구체적으로 수정해주세요.");
_btnPanel.Children.Add(reconfirmBtn);
var cancelBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
var cancelBtn = CreateActionButton("\uE711", "취소", cancelBrush, cancelBrush, false);
var cancelBtn = CreateActionButton("\uE711", rejectLabel, cancelBrush, cancelBrush, false);
cancelBtn.MouseLeftButtonUp += (_, _) => { _tcs?.TrySetResult("취소"); Hide(); };
_btnPanel.Children.Add(cancelBtn);
}
@@ -948,4 +1005,22 @@ internal sealed class PlanViewerWindow : Window
btn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
return btn;
}
private static string ResolveUiExpressionLevel()
{
if (Application.Current is App app)
return NormalizeUiExpressionLevel(app.SettingsService?.Settings?.Llm.AgentUiExpressionLevel);
return "balanced";
}
private static string NormalizeUiExpressionLevel(string? value)
{
return (value ?? "balanced").Trim().ToLowerInvariant() switch
{
"rich" => "rich",
"simple" => "simple",
_ => "balanced",
};
}
}

View File

@@ -538,7 +538,7 @@
<!-- ══════════════════════════════════════════════════════════════════ -->
<!-- 탭 컨트롤 (좌측 사이드바) -->
<!-- ══════════════════════════════════════════════════════════════════ -->
<TabControl Grid.Row="0" TabStripPlacement="Left"
<TabControl x:Name="MainSettingsTab" Grid.Row="0" TabStripPlacement="Left"
Background="Transparent" BorderThickness="0">
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
@@ -574,6 +574,35 @@
</Border>
<Rectangle DockPanel.Dock="Top" Height="1"
Fill="{DynamicResource BorderColor}" Margin="12,0,12,8"/>
<Border DockPanel.Dock="Bottom" Margin="12,0,12,8"
Visibility="Collapsed"
Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1"
CornerRadius="10" Padding="8,8">
<StackPanel>
<TextBlock Text="설정 표현"
FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource SecondaryText}"
Margin="2,0,0,6"/>
<WrapPanel>
<RadioButton x:Name="DisplayModeRich"
Content="풍부하게"
GroupName="DisplayMode"
Style="{StaticResource AgentSubTabStyle}"
Checked="DisplayMode_Checked"/>
<RadioButton x:Name="DisplayModeBalanced"
Content="적절하게"
GroupName="DisplayMode"
Style="{StaticResource AgentSubTabStyle}"
Checked="DisplayMode_Checked"/>
<RadioButton x:Name="DisplayModeSimple"
Content="간단하게"
GroupName="DisplayMode"
Style="{StaticResource AgentSubTabStyle}"
Checked="DisplayMode_Checked"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 탭 목록 — 창 높이가 충분히 크므로 스크롤 없음 -->
<ScrollViewer VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled"
@@ -621,7 +650,7 @@
<StackPanel>
<TextBlock Text="AI 기능" Style="{StaticResource SectionHeader}"/>
<Border Style="{StaticResource SettingsRow}">
<Border Style="{StaticResource SettingsRow}" Visibility="Collapsed">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -3258,7 +3287,7 @@
<!-- ═════════════════════════════════════════════════════════════ -->
<!-- AX Agent 탭 -->
<!-- ═════════════════════════════════════════════════════════════ -->
<TabItem x:Name="AgentTabItem" Header="AX Agent" Tag="&#xE8BD;" Style="{StaticResource SideNavItem}">
<TabItem x:Name="AgentTabItem" Header="AX Agent" Tag="&#xE8BD;" Style="{StaticResource SideNavItem}" Visibility="Collapsed">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -3540,6 +3569,16 @@
Text="{Binding VllmApiKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
<TextBlock Style="{StaticResource RowLabel}" Text="SSL 인증서 검증 생략"/>
<TextBlock Style="{StaticResource RowHint}" Text="사내 자체 인증서 환경에서만 사용하세요. vLLM HTTPS 인증서 오류를 무시합니다."/>
</StackPanel>
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding VllmAllowInsecureTls, Mode=TwoWay}"/>
</Grid>
</Border>
</StackPanel>
<!-- Gemini 패널 -->
@@ -4019,6 +4058,7 @@
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="슬래시 팝업 표시 개수"/>
<TextBlock Style="{StaticResource RowHint}" Text="/ 입력 시 한 번에 보여줄 명령어 개수입니다. (최소 3 ~ 최대 20)"/>
<TextBlock Style="{StaticResource RowHint}" Text="저장 후 즉시 반영"/>
</StackPanel>
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center"
Orientation="Horizontal">
@@ -4035,6 +4075,50 @@
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="슬래시 핀 최대 개수"/>
<TextBlock Style="{StaticResource RowHint}" Text="핀 고정 가능한 슬래시 명령어 최대 개수입니다. (최소 1 ~ 최대 30)"/>
<TextBlock Style="{StaticResource RowHint}" Text="저장 후 즉시 반영"/>
</StackPanel>
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding MaxFavoriteSlashCommands}"
FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource AccentColor}"
VerticalAlignment="Center"
MinWidth="24" TextAlignment="Right"
Margin="0,0,8,0"/>
<Slider Width="140" Minimum="1" Maximum="30" TickFrequency="1"
IsSnapToTickEnabled="True"
Value="{Binding MaxFavoriteSlashCommands, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="슬래시 최근 최대 개수"/>
<TextBlock Style="{StaticResource RowHint}" Text="최근 사용(MRU)으로 기억할 슬래시 명령어 최대 개수입니다. (최소 5 ~ 최대 50)"/>
<TextBlock Style="{StaticResource RowHint}" Text="저장 후 즉시 반영"/>
</StackPanel>
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding MaxRecentSlashCommands}"
FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource AccentColor}"
VerticalAlignment="Center"
MinWidth="24" TextAlignment="Right"
Margin="0,0,8,0"/>
<Slider Width="140" Minimum="5" Maximum="50" TickFrequency="1"
IsSnapToTickEnabled="True"
Value="{Binding MaxRecentSlashCommands, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Border>
<!-- ── 드래그 앤 드롭 AI ── -->
<TextBlock Style="{StaticResource SectionHeader}" Text="드래그 앤 드롭 AI"/>
<Border Style="{StaticResource SettingsRow}">
@@ -4373,24 +4457,6 @@
</StackPanel>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="오류 시 최대 재시도"/>
<TextBlock Style="{StaticResource RowHint}" Text="도구 실행 실패 시 자동으로 재시도할 횟수입니다. (0~10)"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Slider Width="100" Minimum="0" Maximum="10" TickFrequency="1"
IsSnapToTickEnabled="True"
Value="{Binding MaxRetryOnError, Mode=TwoWay}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding MaxRetryOnError}"
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AccentColor}"
Width="24" TextAlignment="Center" VerticalAlignment="Center"
Margin="8,0,0,0"/>
</StackPanel>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="0,0,180,0">
@@ -4454,6 +4520,32 @@
</ComboBox>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
<TextBlock Style="{StaticResource RowLabel}" Text="사전 컨텍스트 압축"/>
<TextBlock Style="{StaticResource RowHint}" Text="토큰 한도 전에 이전 대화를 요약/정리해 컨텍스트를 안정적으로 유지합니다."/>
</StackPanel>
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding EnableProactiveContextCompact, Mode=TwoWay}"/>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="압축 시작 임계치"/>
<TextBlock Style="{StaticResource RowHint}" Text="최대 컨텍스트 토큰 대비 사용률이 이 값에 도달하면 사전 압축을 수행합니다."/>
</StackPanel>
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center" Orientation="Horizontal">
<TextBlock Text="{Binding ContextCompactTriggerPercent, StringFormat={}{0}%}"
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AccentColor}"
VerticalAlignment="Center" MinWidth="42" TextAlignment="Right" Margin="0,0,8,0"/>
<Slider Width="140" Minimum="50" Maximum="95" TickFrequency="5"
IsSnapToTickEnabled="True"
Value="{Binding ContextCompactTriggerPercent, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
@@ -4698,9 +4790,9 @@
<ComboBox HorizontalAlignment="Right" VerticalAlignment="Center"
Width="180" SelectedValue="{Binding FolderDataUsage, Mode=TwoWay}"
SelectedValuePath="Tag">
<ComboBoxItem Content="적극 활용 (자동 탐색)" Tag="active"/>
<ComboBoxItem Content="소극 활용 (요청 시)" Tag="passive"/>
<ComboBoxItem Content="활용하지 않음" Tag="none"/>
<ComboBoxItem Content="소극 활용 (요청 시)" Tag="passive"/>
<ComboBoxItem Content="적극 활용 (자동 탐색)" Tag="active"/>
</ComboBox>
</Grid>
</Border>
@@ -5211,6 +5303,24 @@
HorizontalAlignment="Center"/>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="오류 재시도 횟수"/>
<TextBlock Style="{StaticResource RowHint}" Text="도구 실행 실패 시 자동 복구를 위해 재시도할 최대 횟수입니다. (0~10)"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Slider Width="100" Minimum="0" Maximum="10" TickFrequency="1"
IsSnapToTickEnabled="True"
Value="{Binding MaxRetryOnError, Mode=TwoWay}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding MaxRetryOnError}"
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AccentColor}"
Width="24" TextAlignment="Center" VerticalAlignment="Center"
Margin="8,0,0,0"/>
</StackPanel>
</Grid>
</Border>
<!-- ── 모델 폴백 ── -->
<TextBlock Style="{StaticResource SectionHeader}" Text="모델 폴백"/>

View File

@@ -15,6 +15,7 @@ public partial class SettingsWindow : Window
private readonly Action<string> _previewCallback;
private readonly Action _revertCallback;
private bool _saved;
private bool _isDisplayModeSyncing;
/// <summary>
/// 핫키 녹화 시작/종료를 외부(App.xaml.cs)에 알리는 콜백.
@@ -81,9 +82,153 @@ public partial class SettingsWindow : Window
// AI 기능 토글 초기화
ApplyAiEnabledState(app?.SettingsService?.Settings.AiEnabled ?? false, init: true);
ApplyOperationModeState(app?.SettingsService?.Settings.OperationMode);
InitializeDisplayModeUi();
};
}
private void InitializeDisplayModeUi()
{
var app = System.Windows.Application.Current as App;
if (app?.SettingsService?.Settings?.Llm != null)
app.SettingsService.Settings.Llm.AgentUiExpressionLevel = "rich";
SetDisplayMode("rich", persist: false);
}
private void DisplayMode_Checked(object sender, RoutedEventArgs e)
{
if (_isDisplayModeSyncing) return;
var rb = sender as RadioButton;
var next = rb?.Name switch
{
"DisplayModeRich" => "rich",
"DisplayModeSimple" => "simple",
_ => "balanced",
};
SetDisplayMode(next, persist: true);
}
private void SetDisplayMode(string mode, bool persist)
{
mode = NormalizeDisplayMode(mode);
_isDisplayModeSyncing = true;
try
{
var rich = GetDisplayModeRadio("DisplayModeRich");
var balanced = GetDisplayModeRadio("DisplayModeBalanced");
var simple = GetDisplayModeRadio("DisplayModeSimple");
if (rich != null) rich.IsChecked = mode == "rich";
if (balanced != null) balanced.IsChecked = mode == "balanced";
if (simple != null) simple.IsChecked = mode == "simple";
}
finally
{
_isDisplayModeSyncing = false;
}
ApplyMainTabVisibility(mode);
ApplyAgentSubTabVisibility(mode);
if (!persist) return;
var app = System.Windows.Application.Current as App;
if (app?.SettingsService?.Settings?.Llm == null) return;
app.SettingsService.Settings.Llm.AgentUiExpressionLevel = mode;
app.SettingsService.Save();
}
private void ApplyMainTabVisibility(string mode)
{
if (MainSettingsTab == null) return;
var simpleKeep = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"일반", "테마", "기능", "AX Agent"
};
var balancedKeep = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"일반", "테마", "클립보드", "캡처", "시스템", "기능", "AX Agent"
};
foreach (var item in MainSettingsTab.Items.OfType<TabItem>())
{
var header = item.Header?.ToString() ?? "";
var visible = mode switch
{
"simple" => simpleKeep.Contains(header),
"balanced" => balancedKeep.Contains(header),
_ => true,
};
if (string.Equals(header, "알림", StringComparison.OrdinalIgnoreCase))
visible = false;
if (string.Equals(header, "AX Agent", StringComparison.OrdinalIgnoreCase)
&& !((System.Windows.Application.Current as App)?.SettingsService?.Settings.AiEnabled ?? false))
{
visible = false;
}
item.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}
if (MainSettingsTab.SelectedItem is TabItem selected && selected.Visibility == Visibility.Visible)
return;
var firstVisible = MainSettingsTab.Items.OfType<TabItem>().FirstOrDefault(t => t.Visibility == Visibility.Visible);
if (firstVisible != null)
MainSettingsTab.SelectedItem = firstVisible;
}
private void ApplyAgentSubTabVisibility(string mode)
{
if (AgentTabCommon == null) return;
SetSubTabVisible(AgentTabCommon, true);
SetSubTabVisible(AgentTabChat, mode != "simple");
SetSubTabVisible(AgentTabCoworkCode, true);
SetSubTabVisible(AgentTabCowork, mode == "rich");
SetSubTabVisible(AgentTabCode, mode == "rich");
SetSubTabVisible(AgentTabTools, mode != "simple");
SetSubTabVisible(AgentTabEtc, mode != "simple");
SetSubTabVisible(AgentTabDev, mode == "rich");
if (AgentTabCommon.IsChecked != true
&& AgentTabChat.IsChecked != true
&& AgentTabCoworkCode.IsChecked != true
&& AgentTabCowork.IsChecked != true
&& AgentTabCode.IsChecked != true
&& AgentTabTools.IsChecked != true
&& AgentTabEtc.IsChecked != true
&& AgentTabDev.IsChecked != true)
{
AgentTabCommon.IsChecked = true;
}
AgentSubTab_Checked(this, new RoutedEventArgs());
}
private static void SetSubTabVisible(RadioButton? tab, bool visible)
{
if (tab == null) return;
tab.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
if (!visible) tab.IsChecked = false;
}
private static string NormalizeDisplayMode(string? mode)
{
return (mode ?? "balanced").Trim().ToLowerInvariant() switch
{
"rich" => "rich",
"simple" => "simple",
_ => "balanced",
};
}
private RadioButton? GetDisplayModeRadio(string name)
{
if (MainSettingsTab?.Template == null) return null;
return MainSettingsTab.Template.FindName(name, MainSettingsTab) as RadioButton;
}
// ─── 에이전트 차단 섹션 → 기타 탭 이동 ──────────────────────────────────────
private void MoveBlockSectionToEtc()
@@ -1558,6 +1703,7 @@ public partial class SettingsWindow : Window
Service = currentService,
Endpoint = dlg.Endpoint,
ApiKey = dlg.ApiKey,
AllowInsecureTls = dlg.AllowInsecureTls,
AuthType = dlg.AuthType,
Cp4dUrl = dlg.Cp4dUrl,
Cp4dUsername = dlg.Cp4dUsername,
@@ -1576,7 +1722,7 @@ public partial class SettingsWindow : Window
var currentService = GetCurrentServiceSubTab();
var dlg = new ModelRegistrationDialog(currentService, row.Alias, currentModel,
row.Endpoint, row.ApiKey,
row.Endpoint, row.ApiKey, row.AllowInsecureTls,
row.AuthType ?? "bearer", row.Cp4dUrl ?? "", row.Cp4dUsername ?? "", cp4dPw);
dlg.Owner = this;
if (dlg.ShowDialog() == true)
@@ -1586,6 +1732,7 @@ public partial class SettingsWindow : Window
row.Service = currentService;
row.Endpoint = dlg.Endpoint;
row.ApiKey = dlg.ApiKey;
row.AllowInsecureTls = dlg.AllowInsecureTls;
row.AuthType = dlg.AuthType;
row.Cp4dUrl = dlg.Cp4dUrl;
row.Cp4dUsername = dlg.Cp4dUsername;
@@ -2051,9 +2198,9 @@ public partial class SettingsWindow : Window
{
AiEnabledToggle.IsChecked = enabled;
}
// AX Agent 탭 가시성
// AX Agent 상세 설정은 전용 창으로 분리 (탭은 숨김)
if (AgentTabItem != null)
AgentTabItem.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
AgentTabItem.Visibility = Visibility.Collapsed;
}
private void ApplyOperationModeState(string? mode)