AX Agent 프리뷰 UI를 claw-code 스타일로 정리하고 프리뷰 surface를 공통화
Some checks failed
Release Gate / gate (push) Has been cancelled

- AX Agent 권한 승인 프리뷰에 공통 preview surface helper를 도입해 제목/요약/본문 box 구성을 일관되게 정리함
- 우측 파일 프리뷰 패널에 파일명, 경로, 형식·크기 메타 헤더를 추가하고 텍스트 프리뷰를 bordered preview box 안에 렌더하도록 개선함
- README와 DEVELOPMENT 문서에 2026-04-06 01:08 (KST) 기준 변경 이력 반영 완료
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 경고 0 / 오류 0 확인
This commit is contained in:
2026-04-05 22:03:16 +09:00
parent 53965083e3
commit 1948af3cc4
6 changed files with 239 additions and 139 deletions

View File

@@ -0,0 +1,85 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace AxCopilot.Views;
internal static class AgentPreviewSurfaceFactory
{
internal static Border CreateSurface(
string title,
string summary,
UIElement body,
Brush primary,
Brush secondary,
Brush itemBackground,
Brush borderBrush,
Thickness? margin = null)
{
return new Border
{
Background = itemBackground,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(12),
Padding = new Thickness(12, 10, 12, 10),
Margin = margin ?? new Thickness(0, 8, 0, 0),
Child = new StackPanel
{
Children =
{
new TextBlock
{
Text = title,
FontSize = 12,
FontWeight = FontWeights.SemiBold,
Foreground = primary,
},
new TextBlock
{
Text = summary,
FontSize = 11,
Foreground = secondary,
Margin = new Thickness(0, 2, 0, 8),
TextWrapping = TextWrapping.Wrap,
},
body
}
}
};
}
internal static Border CreatePreviewBox(
string content,
Brush primary,
Brush secondary,
Brush borderBrush,
double maxHeight,
bool monospace = true)
{
var panel = new Border
{
Background = Brushes.Transparent,
BorderBrush = new SolidColorBrush(Color.FromArgb(0x20, 0x80, 0x80, 0x80)),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(8, 6, 8, 6),
Child = new ScrollViewer
{
MaxHeight = maxHeight,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Content = new TextBlock
{
Text = string.IsNullOrWhiteSpace(content) ? "(empty)" : content,
Foreground = primary,
FontFamily = monospace ? new FontFamily("Consolas") : new FontFamily("Pretendard"),
FontSize = 11.2,
TextWrapping = TextWrapping.Wrap,
LineHeight = 18,
}
}
};
return panel;
}
}

View File

@@ -5240,43 +5240,88 @@
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1,0,0,0">
<DockPanel LastChildFill="True">
<!-- 탭 바 + 도구 버튼 (DockPanel.Top — WebView2 위에 독립 레이어) -->
<Border DockPanel.Dock="Top" Background="{DynamicResource HintBackground}"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="0,0,0,1"
Padding="0" Panel.ZIndex="10" Focusable="True"
PreviewMouseDown="PreviewTabBar_PreviewMouseDown">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Disabled" CanContentScroll="True">
<StackPanel x:Name="PreviewTabPanel" Orientation="Horizontal"/>
</ScrollViewer>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="4,0">
<Button x:Name="BtnOpenExternal" Style="{StaticResource GhostBtn}" Padding="6"
Click="BtnOpenExternal_Click" ToolTip="외부 프로그램으로 열기" Cursor="Hand">
<TextBlock Text="&#xE8A7;" FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="{DynamicResource SecondaryText}" IsHitTestVisible="False"/>
</Button>
<Button Style="{StaticResource GhostBtn}" Padding="6"
Click="BtnClosePreview_Click" ToolTip="미리보기 닫기" Cursor="Hand">
<TextBlock Text="&#xE711;" FontFamily="Segoe MDL2 Assets" FontSize="10"
Foreground="{DynamicResource SecondaryText}" IsHitTestVisible="False"/>
</Button>
</StackPanel>
</Grid>
Padding="12,10,12,8">
<StackPanel>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock x:Name="PreviewHeaderTitle"
Text="미리보기"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="PreviewHeaderSubtitle"
Text="선택한 파일이 여기에 표시됩니다"
FontSize="11.5"
Margin="0,2,0,0"
Foreground="{DynamicResource SecondaryText}"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="8,0,0,0">
<Button x:Name="BtnOpenExternal" Style="{StaticResource GhostBtn}" Padding="6"
Click="BtnOpenExternal_Click" ToolTip="외부 프로그램으로 열기" Cursor="Hand">
<TextBlock Text="&#xE8A7;" FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="{DynamicResource SecondaryText}" IsHitTestVisible="False"/>
</Button>
<Button Style="{StaticResource GhostBtn}" Padding="6"
Click="BtnClosePreview_Click" ToolTip="미리보기 닫기" Cursor="Hand">
<TextBlock Text="&#xE711;" FontFamily="Segoe MDL2 Assets" FontSize="10"
Foreground="{DynamicResource SecondaryText}" IsHitTestVisible="False"/>
</Button>
</StackPanel>
</Grid>
<Border Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1"
CornerRadius="10"
Padding="0"
Panel.ZIndex="10"
Focusable="True"
PreviewMouseDown="PreviewTabBar_PreviewMouseDown">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Disabled" CanContentScroll="True">
<StackPanel x:Name="PreviewTabPanel" Orientation="Horizontal"/>
</ScrollViewer>
<Border Grid.Column="1"
Margin="6,6,6,6"
Padding="8,4"
Background="{DynamicResource HintBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="999">
<TextBlock x:Name="PreviewHeaderMeta"
Text="파일 메타"
FontSize="10.5"
Foreground="{DynamicResource SecondaryText}"/>
</Border>
</Grid>
</Border>
</StackPanel>
</Border>
<!-- 미리보기 콘텐츠 영역 (DockPanel.Fill — WebView2는 여기에만 존재) -->
<Grid ClipToBounds="True">
<Grid ClipToBounds="True" Background="{DynamicResource LauncherBackground}">
<wv2:WebView2 x:Name="PreviewWebView" Visibility="Collapsed"/>
<ScrollViewer x:Name="PreviewTextScroll" Visibility="Collapsed"
VerticalScrollBarVisibility="Auto" Padding="12,8">
<TextBlock x:Name="PreviewTextBlock" TextWrapping="Wrap"
FontFamily="Consolas" FontSize="12"
Foreground="{DynamicResource PrimaryText}"/>
VerticalScrollBarVisibility="Auto" Padding="16,14">
<Border Background="{DynamicResource HintBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="12"
Padding="12,10">
<TextBlock x:Name="PreviewTextBlock" TextWrapping="Wrap"
FontFamily="Consolas" FontSize="12"
Foreground="{DynamicResource PrimaryText}"/>
</Border>
</ScrollViewer>
<DataGrid x:Name="PreviewDataGrid" Visibility="Collapsed"
AutoGenerateColumns="True" IsReadOnly="True"

View File

@@ -17460,6 +17460,7 @@ public partial class ChatWindow : Window
private async void LoadPreviewContent(string filePath)
{
var ext = System.IO.Path.GetExtension(filePath).ToLowerInvariant();
SetPreviewHeader(filePath);
// 모든 콘텐츠 숨기기
PreviewWebView.Visibility = Visibility.Collapsed;
@@ -17469,6 +17470,7 @@ public partial class ChatWindow : Window
if (!System.IO.File.Exists(filePath))
{
SetPreviewHeaderState("파일을 찾을 수 없습니다");
PreviewEmpty.Text = "파일을 찾을 수 없습니다";
PreviewEmpty.Visibility = Visibility.Visible;
return;
@@ -17510,6 +17512,7 @@ public partial class ChatWindow : Window
break;
default:
SetPreviewHeaderState("지원되지 않는 형식");
PreviewEmpty.Text = "미리보기할 수 없는 파일 형식입니다";
PreviewEmpty.Visibility = Visibility.Visible;
break;
@@ -17517,11 +17520,39 @@ public partial class ChatWindow : Window
}
catch (Exception ex)
{
SetPreviewHeaderState("미리보기 오류");
PreviewTextBlock.Text = $"미리보기 오류: {ex.Message}";
PreviewTextScroll.Visibility = Visibility.Visible;
}
}
private void SetPreviewHeader(string filePath)
{
if (PreviewHeaderTitle == null || PreviewHeaderSubtitle == null || PreviewHeaderMeta == null)
return;
var fileName = System.IO.Path.GetFileName(filePath);
var extension = System.IO.Path.GetExtension(filePath).TrimStart('.').ToUpperInvariant();
var fileInfo = new System.IO.FileInfo(filePath);
var sizeText = fileInfo.Exists
? fileInfo.Length >= 1024 * 1024
? $"{fileInfo.Length / 1024d / 1024d:F1} MB"
: $"{Math.Max(1, fileInfo.Length / 1024d):F0} KB"
: "파일 없음";
PreviewHeaderTitle.Text = string.IsNullOrWhiteSpace(fileName) ? "미리보기" : fileName;
PreviewHeaderSubtitle.Text = filePath;
PreviewHeaderMeta.Text = string.IsNullOrWhiteSpace(extension)
? sizeText
: $"{extension} · {sizeText}";
}
private void SetPreviewHeaderState(string state)
{
if (PreviewHeaderMeta != null && !string.IsNullOrWhiteSpace(state))
PreviewHeaderMeta.Text = state;
}
private bool _webViewInitialized;
private static readonly string WebView2DataFolder =
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
@@ -17617,6 +17648,9 @@ public partial class ChatWindow : Window
{
_previewTabs.Clear();
_activePreviewTab = null;
if (PreviewHeaderTitle != null) PreviewHeaderTitle.Text = "미리보기";
if (PreviewHeaderSubtitle != null) PreviewHeaderSubtitle.Text = "선택한 파일이 여기에 표시됩니다";
if (PreviewHeaderMeta != null) PreviewHeaderMeta.Text = "파일 메타";
PreviewColumn.Width = new GridLength(0);
SplitterColumn.Width = new GridLength(0);
PreviewPanel.Visibility = Visibility.Collapsed;

View File

@@ -581,48 +581,19 @@ internal sealed class PermissionRequestWindow : Window
Brush border,
UiExpressionProfile uiProfile)
{
return new Border
{
Background = itemBg,
BorderBrush = border,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(10),
Padding = new Thickness(10, 8, 10, 8),
Margin = new Thickness(0, 8, 0, 0),
Child = new StackPanel
{
Children =
{
new TextBlock
{
Text = title,
FontSize = 11.5,
FontWeight = FontWeights.SemiBold,
Foreground = primary,
},
new TextBlock
{
Text = summary,
FontSize = 11,
Foreground = secondary,
Margin = new Thickness(0, 2, 0, 6),
},
new TextBox
{
Text = content,
IsReadOnly = true,
BorderThickness = new Thickness(0),
Background = Brushes.Transparent,
Foreground = primary,
FontFamily = new FontFamily("Consolas"),
FontSize = 11,
MaxHeight = uiProfile.PreviewMaxHeight,
TextWrapping = TextWrapping.Wrap,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
}
}
}
};
return AgentPreviewSurfaceFactory.CreateSurface(
title,
summary,
AgentPreviewSurfaceFactory.CreatePreviewBox(
content,
primary,
secondary,
border,
uiProfile.PreviewMaxHeight),
primary,
secondary,
itemBg,
border);
}
private static Border BuildFileEditPreviewCard(
@@ -679,41 +650,19 @@ internal sealed class PermissionRequestWindow : Window
});
}
return new Border
{
Background = itemBg,
BorderBrush = border,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(10),
Padding = new Thickness(10, 8, 10, 8),
Margin = new Thickness(0, 8, 0, 0),
Child = new StackPanel
{
Children =
{
new TextBlock
{
Text = title,
FontSize = 11.5,
FontWeight = FontWeights.SemiBold,
Foreground = primary,
},
new TextBlock
{
Text = summary,
FontSize = 11,
Foreground = secondary,
Margin = new Thickness(0, 2, 0, 6),
},
new ScrollViewer
{
MaxHeight = uiProfile.PreviewMaxHeight,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Content = body,
}
}
}
};
return AgentPreviewSurfaceFactory.CreateSurface(
title,
summary,
AgentPreviewSurfaceFactory.CreatePreviewBox(
string.Join("\n", body.Children.OfType<TextBlock>().Select(tb => tb.Text)),
primary,
secondary,
border,
uiProfile.PreviewMaxHeight),
primary,
secondary,
itemBg,
border);
}
private static Border BuildFileWriteTwoColumnPreviewCard(
@@ -764,36 +713,14 @@ internal sealed class PermissionRequestWindow : Window
Grid.SetColumn(newPanel, 2);
grid.Children.Add(newPanel);
return new Border
{
Background = itemBg,
BorderBrush = border,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(10),
Padding = new Thickness(10, 8, 10, 8),
Margin = new Thickness(0, 8, 0, 0),
Child = new StackPanel
{
Children =
{
new TextBlock
{
Text = title,
FontSize = 11.5,
FontWeight = FontWeights.SemiBold,
Foreground = primary,
},
new TextBlock
{
Text = summary,
FontSize = 11,
Foreground = secondary,
Margin = new Thickness(0, 2, 0, 6),
},
grid,
}
}
};
return AgentPreviewSurfaceFactory.CreateSurface(
title,
summary,
grid,
primary,
secondary,
itemBg,
border);
}
private static Border BuildWriteColumn(
@@ -1161,8 +1088,8 @@ internal sealed class PermissionRequestWindow : Window
Owner = owner,
};
if (owner.Resources.MergedDictionaries.Count > 0)
dialog.Resources.MergedDictionaries.Add(owner.Resources);
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
dialog.Resources.MergedDictionaries.Add(owner.Resources);
dialog.ShowDialog();
return dialog._result;