AX Agent 프리뷰 UI를 claw-code 스타일로 정리하고 프리뷰 surface를 공통화
Some checks failed
Release Gate / gate (push) Has been cancelled
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:
85
src/AxCopilot/Views/AgentPreviewSurfaceFactory.cs
Normal file
85
src/AxCopilot/Views/AgentPreviewSurfaceFactory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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="" 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="" 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="" 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="" 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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user