Compare commits

..

2 Commits

Author SHA1 Message Date
be7328184a 코드 탭 폴더 데이터 활용 기본 허용 및 파일명 강조 적용
Some checks failed
Release Gate / gate (push) Has been cancelled
코드 탭에서는 폴더 내 데이터 활용을 항상 적극 활용으로 고정하고 하단 버튼과 내부 설정 옵션을 숨겨 사용 흐름을 단순화했습니다.

코워크와 코드 탭의 사용자 메시지도 파일 경로 강조 렌더러를 사용하도록 바꿔 폴더 하위 파일명 입력 시 파란색으로 표시되게 맞췄습니다.

README와 DEVELOPMENT 문서에 변경 이력을 반영했고 Release 빌드에서 경고 0 오류 0을 확인했습니다.
2026-04-05 22:28:29 +09:00
905ea41ed3 코드 탭 Git 브랜치 선택 UI 단순화
- 하단 Git 브랜치 버튼을 상태판형에서 브랜치 선택 버튼 형태로 정리\n- 브랜치 버튼 기본 노출에서 변경 파일/추가/삭제 수치를 숨기고 브랜치명 중심으로 단순화\n- README 및 DEVELOPMENT 문서 이력 갱신\n\n검증:\n- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\\n- 경고 0 / 오류 0
2026-04-05 22:21:58 +09:00
4 changed files with 277 additions and 97 deletions

View File

@@ -7,6 +7,9 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
개발 참고: Claw Code 동등성 작업 추적 문서 개발 참고: Claw Code 동등성 작업 추적 문서
`docs/claw-code-parity-plan.md` `docs/claw-code-parity-plan.md`
- 업데이트: 2026-04-06 00:50 (KST)
- 채팅/코워크 프리셋 카드의 hover 설명 레이어를 카드 내부 오버레이 방식에서 안정적인 tooltip형 설명으로 바꿨습니다. 카드 배경/테두리만 반응하게 정리해 hover 시 반복 깜빡임을 줄였습니다.
- 업데이트: 2026-04-06 00:45 (KST) - 업데이트: 2026-04-06 00:45 (KST)
- AX Agent 내부 설정 공통 탭에 `대화 스타일` 섹션 제목을 복구해, `문서 형태``디자인 스타일` 저장 항목이 명확히 보이도록 정리했습니다. - AX Agent 내부 설정 공통 탭에 `대화 스타일` 섹션 제목을 복구해, `문서 형태``디자인 스타일` 저장 항목이 명확히 보이도록 정리했습니다.
- AX Agent 내부 설정 스킬 탭의 `MCP 서버` 영역에 `서버 추가` 버튼을 복원했고, 목록 카드 안에서 `활성화/비활성화``삭제`까지 바로 관리할 수 있게 옮겼습니다. - AX Agent 내부 설정 스킬 탭의 `MCP 서버` 영역에 `서버 추가` 버튼을 복원했고, 목록 카드 안에서 `활성화/비활성화``삭제`까지 바로 관리할 수 있게 옮겼습니다.
@@ -1094,3 +1097,10 @@ MIT License
- `claw-code``SessionPreview`/`PreviewBox` 흐름을 참고해 AX Agent 프리뷰도 같은 시각 언어로 정리했다. 새 파일 [AgentPreviewSurfaceFactory.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/AgentPreviewSurfaceFactory.cs)를 추가해 권한 프리뷰 카드의 제목/요약/본문 박스 구조를 공통화했다. - `claw-code``SessionPreview`/`PreviewBox` 흐름을 참고해 AX Agent 프리뷰도 같은 시각 언어로 정리했다. 새 파일 [AgentPreviewSurfaceFactory.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/AgentPreviewSurfaceFactory.cs)를 추가해 권한 프리뷰 카드의 제목/요약/본문 박스 구조를 공통화했다.
- [PermissionRequestWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PermissionRequestWindow.cs)의 일반 프리뷰, 파일 편집 프리뷰, 파일 생성 2열 프리뷰를 이 공통 surface로 맞춰 `preview box` 언어를 통일했다. - [PermissionRequestWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PermissionRequestWindow.cs)의 일반 프리뷰, 파일 편집 프리뷰, 파일 생성 2열 프리뷰를 이 공통 surface로 맞춰 `preview box` 언어를 통일했다.
- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 우측 파일 프리뷰 패널에는 파일명/경로/형식·크기 메타를 보여주는 헤더를 추가하고, 텍스트 프리뷰 본문도 별도 bordered preview box 안에 렌더되게 바꿨다. - [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 우측 파일 프리뷰 패널에는 파일명/경로/형식·크기 메타를 보여주는 헤더를 추가하고, 텍스트 프리뷰 본문도 별도 bordered preview box 안에 렌더되게 바꿨다.
- 업데이트: 2026-04-06 00:35 (KST)
- AX Agent 채팅/코워크 프리셋 카드에서 기본 ToolTip을 제거해 hover 시 깜빡이듯 반복되던 현상을 줄였습니다.
- 업데이트: 2026-04-06 00:42 (KST)
- 코드 탭 하단 Git 브랜치 버튼을 상태판 형태에서 단순한 브랜치 선택 버튼 형태로 정리했습니다.
- 업데이트: 2026-04-05 22:26 (KST)
- 코드 탭에서는 폴더 문서/파일을 기본 작업 전제로 삼도록 `폴더 내 데이터 활용`을 항상 `적극 활용(active)`으로 강제했다. 하단 채팅창의 데이터 활용 버튼은 코드 탭에서 숨기고, 내부 설정 오버레이의 같은 옵션도 코드 탭에서는 노출하지 않게 정리했다.
- 코워크/코드 탭의 사용자 메시지도 assistant 메시지와 같은 파일 경로 강조 렌더러를 쓰도록 바꿔, 폴더 하위 파일명이나 경로를 입력하면 채팅 본문에서 파란색으로 인식되게 맞췄다.

View File

@@ -4851,5 +4851,14 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- [PermissionRequestWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PermissionRequestWindow.cs) 의 `BuildPreviewCard`, `BuildFileEditPreviewCard`, `BuildFileWriteTwoColumnPreviewCard`는 이 helper를 쓰도록 정리해 권한 승인 프리뷰가 같은 preview 언어를 따르도록 맞췄다. - [PermissionRequestWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PermissionRequestWindow.cs) 의 `BuildPreviewCard`, `BuildFileEditPreviewCard`, `BuildFileWriteTwoColumnPreviewCard`는 이 helper를 쓰도록 정리해 권한 승인 프리뷰가 같은 preview 언어를 따르도록 맞췄다.
- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 우측 프리뷰 패널 헤더를 `파일명 / 경로 / 형식·크기 메타` 구조로 재배치하고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `SetPreviewHeader`, `SetPreviewHeaderState`를 추가해 현재 탭 파일 메타를 표시하도록 했다. - [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 우측 프리뷰 패널 헤더를 `파일명 / 경로 / 형식·크기 메타` 구조로 재배치하고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `SetPreviewHeader`, `SetPreviewHeaderState`를 추가해 현재 탭 파일 메타를 표시하도록 했다.
- 텍스트 프리뷰 본문도 bordered preview box 안에 렌더되게 바꿔, AX Agent 파일 프리뷰와 transcript/승인 프리뷰 사이 시각 언어를 더 가깝게 맞췄다. - 텍스트 프리뷰 본문도 bordered preview box 안에 렌더되게 바꿔, AX Agent 파일 프리뷰와 transcript/승인 프리뷰 사이 시각 언어를 더 가깝게 맞췄다.
- Document update: 2026-04-06 00:50 (KST) - Reworked Chat/Cowork preset hover behavior in `BuildTopicButtons()`. The previous inline hover label overlay was removed and replaced with a stable tooltip-style description so preset cards only change background/border on hover, eliminating the repeated flicker effect caused by overlay-style label toggling.
- Document update: 2026-04-06 00:45 (KST) - Restored the missing AX Agent internal-settings UX for conversation-style persistence by adding a visible `대화 스타일` section header above the existing `문서 형태` and `디자인 스타일` controls. The underlying save path already existed in `CmbOverlayDefaultOutputFormat_SelectionChanged` and `CmbOverlayDefaultMood_SelectionChanged`; this pass makes that persisted behavior explicit again in the overlay. - Document update: 2026-04-06 00:45 (KST) - Restored the missing AX Agent internal-settings UX for conversation-style persistence by adding a visible `대화 스타일` section header above the existing `문서 형태` and `디자인 스타일` controls. The underlying save path already existed in `CmbOverlayDefaultOutputFormat_SelectionChanged` and `CmbOverlayDefaultMood_SelectionChanged`; this pass makes that persisted behavior explicit again in the overlay.
- Document update: 2026-04-06 00:45 (KST) - Restored MCP server management inside the AX Agent internal settings overlay. The skill tab now exposes `+ 서버 추가`, and overlay MCP cards support inline enable/disable and delete actions with immediate persistence through `PersistOverlaySettingsState(...)`. - Document update: 2026-04-06 00:45 (KST) - Restored MCP server management inside the AX Agent internal settings overlay. The skill tab now exposes `+ 서버 추가`, and overlay MCP cards support inline enable/disable and delete actions with immediate persistence through `PersistOverlaySettingsState(...)`.
- 업데이트: 2026-04-06 00:35 (KST)
- `ChatWindow.xaml.cs`의 프리셋 카드 hover 처리에서 WPF 기본 ToolTip을 제거했습니다. 이제 hover는 배경/테두리만 바뀌고 tooltip에 의한 enter/leave 반복이 줄어듭니다.
- 업데이트: 2026-04-06 00:42 (KST)
- `ChatWindow` 하단 Git 브랜치 UI를 단순화했습니다. 변경 파일 수/추가/삭제 수치는 기본 버튼에서는 숨기고 브랜치명 중심의 선택 버튼처럼 보이게 조정했습니다.
- 업데이트: 2026-04-05 22:26 (KST)
- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `IsFolderDataAlwaysEnabledTab()` helper를 추가하고, 코드 탭에서는 `_folderDataUsage`를 항상 `active`로 로드/저장하도록 고정했다. 이에 맞춰 `BtnDataUsage_Click`, `UpdateDataUsageUI()`, 오버레이의 `OverlayFolderDataUsageRow`, `BtnOverlayFolderDataUsage_Click`, `CmbOverlayFolderDataUsage_SelectionChanged`도 코드 탭에서는 숨김 또는 강제 active만 유지하게 정리했다.
- 같은 파일의 사용자 메시지 bubble 렌더에서 코워크/코드 탭은 plain `TextBlock` 대신 [MarkdownRenderer.Render](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/MarkdownRenderer.cs) 경로를 타도록 변경했다. 이로써 코워크/코드 사용자 입력 안의 파일명/파일 경로가 assistant 응답과 동일한 파란 강조 규칙을 쓰게 됐다.

View File

@@ -2245,11 +2245,11 @@
Margin="2,0,0,0" Margin="2,0,0,0"
Visibility="Collapsed" Visibility="Collapsed"
Click="BtnGitBranch_Click" Click="BtnGitBranch_Click"
ToolTip="현재 Git 브랜치 상태"> ToolTip="Git 브랜치 선택">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE943;" <TextBlock Text="&#xE943;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="12" FontSize="12.5"
Foreground="{DynamicResource SecondaryText}" Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0,0,4,0"/> Margin="0,0,4,0"/>
@@ -2260,18 +2260,21 @@
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<TextBlock x:Name="GitBranchFilesText" <TextBlock x:Name="GitBranchFilesText"
Text="" Text=""
Visibility="Collapsed"
FontSize="11" FontSize="11"
Foreground="{DynamicResource SecondaryText}" Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="6,0,0,0"/> Margin="6,0,0,0"/>
<TextBlock x:Name="GitBranchAddedText" <TextBlock x:Name="GitBranchAddedText"
Text="" Text=""
Visibility="Collapsed"
FontSize="11" FontSize="11"
Foreground="#16A34A" Foreground="#16A34A"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="6,0,0,0"/> Margin="6,0,0,0"/>
<TextBlock x:Name="GitBranchDeletedText" <TextBlock x:Name="GitBranchDeletedText"
Text="" Text=""
Visibility="Collapsed"
FontSize="11" FontSize="11"
Foreground="#DC2626" Foreground="#DC2626"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -3250,9 +3253,103 @@
Background="{DynamicResource LauncherBackground}" Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}" BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1" BorderThickness="1"
Foreground="{DynamicResource PrimaryText}" Foreground="{DynamicResource PrimaryText}"
FontSize="12"/> FontSize="12"/>
</Grid> </Grid>
<StackPanel Margin="0,2,0,12">
<TextBlock Text="대화 관리"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="대화 보관 기간과 저장 공간 정리를 여기서 바로 관리합니다."
Margin="0,4,0,0"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,12,0">
<TextBlock Text="대화 보관 기간"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="설정 기간이 지나면 오래된 대화는 자동으로 정리됩니다."
Margin="0,4,0,0"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<WrapPanel Grid.Column="1" HorizontalAlignment="Right">
<Button x:Name="BtnOverlayRetention7"
Content="7일"
Padding="10,6"
Margin="0,0,6,0"
Click="BtnOverlayRetention_Click"/>
<Button x:Name="BtnOverlayRetention30"
Content="30일"
Padding="10,6"
Margin="0,0,6,0"
Click="BtnOverlayRetention_Click"/>
<Button x:Name="BtnOverlayRetention90"
Content="90일"
Padding="10,6"
Margin="0,0,6,0"
Click="BtnOverlayRetention_Click"/>
<Button x:Name="BtnOverlayRetentionUnlimited"
Content="무제한"
Padding="10,6"
Click="BtnOverlayRetention_Click"/>
</WrapPanel>
</Grid>
<Border Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
CornerRadius="12"
Padding="14,12"
Margin="0,0,0,12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="저장 공간"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="OverlayStorageSummaryText"
Text="분석 중..."
Margin="0,6,0,0"
FontSize="12"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="OverlayStorageDriveText"
Margin="0,3,0,0"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Top">
<Button x:Name="BtnOverlayStorageRefresh"
Content="새로고침"
Padding="10,6"
Margin="0,0,8,0"
Click="BtnOverlayStorageRefresh_Click"/>
<Button x:Name="BtnOverlayDeleteAllConversations"
Content="대화 삭제"
Padding="10,6"
Margin="0,0,8,0"
Click="BtnOverlayDeleteAllConversations_Click"/>
<Button x:Name="BtnOverlayStorageCleanup"
Content="저장 공간 줄이기"
Padding="12,6"
Click="BtnOverlayStorageCleanup_Click"/>
</StackPanel>
</Grid>
</Border>
<Border x:Name="OverlayToggleImageInput" Style="{StaticResource OverlayAdvancedToggleRowStyle}"> <Border x:Name="OverlayToggleImageInput" Style="{StaticResource OverlayAdvancedToggleRowStyle}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>

View File

@@ -1813,7 +1813,9 @@ public partial class ChatWindow : Window
: fallbackPermission; : fallbackPermission;
_settings.Settings.Llm.FilePermission = conversationPermission; _settings.Settings.Llm.FilePermission = conversationPermission;
_folderDataUsage = conv?.DataUsage ?? llm.FolderDataUsage ?? "none"; _folderDataUsage = IsFolderDataAlwaysEnabledTab()
? "active"
: (conv?.DataUsage ?? llm.FolderDataUsage ?? "none");
_selectedMood = conv?.Mood ?? llm.DefaultMood ?? "modern"; _selectedMood = conv?.Mood ?? llm.DefaultMood ?? "modern";
} }
@@ -1849,7 +1851,7 @@ public partial class ChatWindow : Window
try try
{ {
var normalizedPermission = PermissionModeCatalog.NormalizeGlobalMode(_settings.Settings.Llm.FilePermission); var normalizedPermission = PermissionModeCatalog.NormalizeGlobalMode(_settings.Settings.Llm.FilePermission);
var dataUsage = _folderDataUsage ?? "none"; var dataUsage = IsFolderDataAlwaysEnabledTab() ? "active" : (_folderDataUsage ?? "none");
var mood = _selectedMood ?? (_settings.Settings.Llm.DefaultMood ?? "modern"); var mood = _selectedMood ?? (_settings.Settings.Llm.DefaultMood ?? "modern");
var outputFormat = conv.OutputFormat ?? "auto"; var outputFormat = conv.OutputFormat ?? "auto";
@@ -2255,6 +2257,8 @@ public partial class ChatWindow : Window
private void BtnDataUsage_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) private void BtnDataUsage_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{ {
if (IsFolderDataAlwaysEnabledTab())
return;
if (DataUsagePopup == null) return; if (DataUsagePopup == null) return;
DataUsageItems.Children.Clear(); DataUsageItems.Children.Clear();
@@ -2331,6 +2335,8 @@ public partial class ChatWindow : Window
private void UpdateDataUsageUI() private void UpdateDataUsageUI()
{ {
if (DataUsageLabel == null || DataUsageIcon == null) return; if (DataUsageLabel == null || DataUsageIcon == null) return;
if (IsFolderDataAlwaysEnabledTab())
_folderDataUsage = "active";
var (label, icon, color) = _folderDataUsage switch var (label, icon, color) = _folderDataUsage switch
{ {
"none" => ("데이터 미활용", "\uE8D8", "#6B7280"), "none" => ("데이터 미활용", "\uE8D8", "#6B7280"),
@@ -2342,6 +2348,7 @@ public partial class ChatWindow : Window
DataUsageIcon.Foreground = BrushFromHex(color); DataUsageIcon.Foreground = BrushFromHex(color);
if (BtnDataUsage != null) if (BtnDataUsage != null)
{ {
BtnDataUsage.Visibility = IsFolderDataAlwaysEnabledTab() ? Visibility.Collapsed : Visibility.Visible;
BtnDataUsage.Background = Brushes.Transparent; BtnDataUsage.Background = Brushes.Transparent;
BtnDataUsage.BorderBrush = Brushes.Transparent; BtnDataUsage.BorderBrush = Brushes.Transparent;
BtnDataUsage.BorderThickness = new Thickness(0); BtnDataUsage.BorderThickness = new Thickness(0);
@@ -2352,8 +2359,13 @@ public partial class ChatWindow : Window
_ => "폴더 문서와 파일을 자동 탐색해 작업에 적극 활용합니다." _ => "폴더 문서와 파일을 자동 탐색해 작업에 적극 활용합니다."
}; };
} }
if (IsFolderDataAlwaysEnabledTab() && DataUsagePopup != null)
DataUsagePopup.IsOpen = false;
} }
private bool IsFolderDataAlwaysEnabledTab()
=> string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase);
/// <summary>Cowork/Code 탭 진입 시 설정의 기본 권한을 적용.</summary> /// <summary>Cowork/Code 탭 진입 시 설정의 기본 권한을 적용.</summary>
private void ApplyTabDefaultPermission() private void ApplyTabDefaultPermission()
{ {
@@ -4172,7 +4184,18 @@ public partial class ChatWindow : Window
CornerRadius = new CornerRadius(9), CornerRadius = new CornerRadius(9),
Padding = new Thickness(11, 7, 11, 7), Padding = new Thickness(11, 7, 11, 7),
HorizontalAlignment = HorizontalAlignment.Right, HorizontalAlignment = HorizontalAlignment.Right,
Child = new TextBlock };
if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase) ||
string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase))
{
var codeBgBrush = TryFindResource("HintBackground") as Brush ?? Brushes.DarkGray;
MarkdownRenderer.EnableFilePathHighlight =
(System.Windows.Application.Current as App)?.SettingsService?.Settings.Llm.EnableFilePathHighlight ?? true;
bubble.Child = MarkdownRenderer.Render(content, primaryText, secondaryText, accentBrush, codeBgBrush);
}
else
{
bubble.Child = new TextBlock
{ {
Text = content, Text = content,
TextAlignment = TextAlignment.Left, TextAlignment = TextAlignment.Left,
@@ -4180,8 +4203,8 @@ public partial class ChatWindow : Window
Foreground = primaryText, Foreground = primaryText,
TextWrapping = TextWrapping.Wrap, TextWrapping = TextWrapping.Wrap,
LineHeight = 18, LineHeight = 18,
} };
}; }
wrapper.Children.Add(bubble); wrapper.Children.Add(bubble);
// 액션 버튼 바 (복사 + 편집, hover 시 표시) // 액션 버튼 바 (복사 + 편집, hover 시 표시)
@@ -12244,7 +12267,7 @@ public partial class ChatWindow : Window
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
void AttachTopicCardHover(Border card, Border hoverLabel, Brush normalBackground, Brush hoverBackground) void AttachTopicCardHover(Border card, Brush normalBackground, Brush hoverBackground)
{ {
card.MouseEnter += (s, _) => card.MouseEnter += (s, _) =>
{ {
@@ -12253,7 +12276,6 @@ public partial class ChatWindow : Window
b.Background = hoverBackground; b.Background = hoverBackground;
b.BorderBrush = TryFindResource("AccentColor") as Brush ?? cardBorder; b.BorderBrush = TryFindResource("AccentColor") as Brush ?? cardBorder;
} }
hoverLabel.Opacity = 1;
}; };
card.MouseLeave += (s, _) => card.MouseLeave += (s, _) =>
{ {
@@ -12262,7 +12284,6 @@ public partial class ChatWindow : Window
b.Background = normalBackground; b.Background = normalBackground;
b.BorderBrush = cardBorder; b.BorderBrush = cardBorder;
} }
hoverLabel.Opacity = 0;
}; };
} }
@@ -12324,31 +12345,6 @@ public partial class ChatWindow : Window
MaxWidth = 112, MaxWidth = 112,
}); });
var hoverLabel = new Border
{
Background = TryFindResource("LauncherBackground") as Brush ?? Brushes.White,
BorderBrush = cardBorder,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(8, 4, 8, 4),
Margin = new Thickness(8, 0, 8, 6),
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Stretch,
Visibility = Visibility.Visible,
Opacity = 0,
IsHitTestVisible = false,
Child = new TextBlock
{
Text = preset.Description,
FontSize = 11.5,
TextWrapping = TextWrapping.Wrap,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxHeight = 34,
Foreground = secondaryText,
TextAlignment = TextAlignment.Center,
}
};
// 커스텀 프리셋: 좌측 상단 뱃지 // 커스텀 프리셋: 좌측 상단 뱃지
if (capturedPreset.IsCustom) if (capturedPreset.IsCustom)
{ {
@@ -12378,10 +12374,8 @@ public partial class ChatWindow : Window
contentGrid.Children.Add(stack); contentGrid.Children.Add(stack);
} }
contentGrid.Children.Add(hoverLabel);
border.Child = contentGrid; border.Child = contentGrid;
AttachTopicCardHover(border, hoverLabel, cardBackground, cardHoverBackground); AttachTopicCardHover(border, cardBackground, cardHoverBackground);
// 클릭 → 해당 주제로 새 대화 시작 // 클릭 → 해당 주제로 새 대화 시작
border.MouseLeftButtonDown += (_, _) => SelectTopic(capturedPreset); border.MouseLeftButtonDown += (_, _) => SelectTopic(capturedPreset);
@@ -12450,36 +12444,9 @@ public partial class ChatWindow : Window
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.Center, TextAlignment = TextAlignment.Center,
}); });
var etcHoverLabel = new Border
{
Background = TryFindResource("LauncherBackground") as Brush ?? Brushes.White,
BorderBrush = cardBorder,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(8, 4, 8, 4),
Margin = new Thickness(8, 0, 8, 6),
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Stretch,
Visibility = Visibility.Visible,
Opacity = 0,
IsHitTestVisible = false,
Child = new TextBlock
{
Text = "프리셋 없이 자유롭게 대화합니다",
FontSize = 11.5,
TextWrapping = TextWrapping.Wrap,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxHeight = 34,
Foreground = secondaryText,
TextAlignment = TextAlignment.Center,
}
};
etcGrid.Children.Add(etcStack); etcGrid.Children.Add(etcStack);
etcGrid.Children.Add(etcHoverLabel);
etcBorder.Child = etcGrid; etcBorder.Child = etcGrid;
AttachTopicCardHover(etcBorder, etcHoverLabel, cardBackground, cardHoverBackground); AttachTopicCardHover(etcBorder, cardBackground, cardHoverBackground);
etcBorder.MouseLeftButtonDown += (_, _) => etcBorder.MouseLeftButtonDown += (_, _) =>
{ {
EmptyState.Visibility = Visibility.Collapsed; EmptyState.Visibility = Visibility.Collapsed;
@@ -12529,35 +12496,9 @@ public partial class ChatWindow : Window
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
}); });
var addHoverLabel = new Border
{
Background = TryFindResource("LauncherBackground") as Brush ?? Brushes.White,
BorderBrush = cardBorder,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(8, 4, 8, 4),
Margin = new Thickness(8, 0, 8, 6),
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Stretch,
Visibility = Visibility.Visible,
Opacity = 0,
IsHitTestVisible = false,
Child = new TextBlock
{
Text = "새 작업 유형 카드를 직접 추가합니다",
FontSize = 11.5,
TextWrapping = TextWrapping.Wrap,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxHeight = 34,
Foreground = secondaryText,
TextAlignment = TextAlignment.Center,
}
};
addGrid.Children.Add(addStack); addGrid.Children.Add(addStack);
addGrid.Children.Add(addHoverLabel);
addBorder.Child = addGrid; addBorder.Child = addGrid;
AttachTopicCardHover(addBorder, addHoverLabel, Brushes.Transparent, cardHoverBackground); AttachTopicCardHover(addBorder, Brushes.Transparent, cardHoverBackground);
addBorder.MouseLeftButtonDown += (_, _) => ShowCustomPresetDialog(); addBorder.MouseLeftButtonDown += (_, _) => ShowCustomPresetDialog();
TopicButtonPanel.Children.Add(addBorder); TopicButtonPanel.Children.Add(addBorder);
} }
@@ -14617,6 +14558,8 @@ public partial class ChatWindow : Window
RefreshOverlayServiceFieldVisibility(service); RefreshOverlayServiceFieldVisibility(service);
BuildOverlayRegisteredModelsPanel(service); BuildOverlayRegisteredModelsPanel(service);
RefreshOverlayAdvancedChoiceButtons(); RefreshOverlayAdvancedChoiceButtons();
RefreshOverlayRetentionButtons();
RefreshOverlayStorageSummary();
} }
finally finally
{ {
@@ -15293,8 +15236,12 @@ public partial class ChatWindow : Window
OverlayModelEditorPanel.Visibility = showBasic ? Visibility.Visible : Visibility.Collapsed; OverlayModelEditorPanel.Visibility = showBasic ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAnchorPermission != null) if (OverlayAnchorPermission != null)
OverlayAnchorPermission.Visibility = showBasic ? Visibility.Visible : Visibility.Collapsed; OverlayAnchorPermission.Visibility = showBasic ? Visibility.Visible : Visibility.Collapsed;
if (IsFolderDataAlwaysEnabledTab())
_folderDataUsage = "active";
if (OverlayFolderDataUsageRow != null) if (OverlayFolderDataUsageRow != null)
OverlayFolderDataUsageRow.Visibility = showShared || showCowork ? Visibility.Visible : Visibility.Collapsed; OverlayFolderDataUsageRow.Visibility = IsFolderDataAlwaysEnabledTab()
? Visibility.Collapsed
: (showShared || showCowork ? Visibility.Visible : Visibility.Collapsed);
if (OverlayTlsRow != null) if (OverlayTlsRow != null)
OverlayTlsRow.Visibility = showChat ? Visibility.Visible : Visibility.Collapsed; OverlayTlsRow.Visibility = showChat ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAnchorAdvanced != null) if (OverlayAnchorAdvanced != null)
@@ -15368,6 +15315,117 @@ public partial class ChatWindow : Window
RefreshOverlayEtcPanels(); RefreshOverlayEtcPanels();
} }
private void RefreshOverlayRetentionButtons()
{
ApplyOverlayRetentionButtonState(BtnOverlayRetention7, _settings.Settings.Llm.RetentionDays == 7);
ApplyOverlayRetentionButtonState(BtnOverlayRetention30, _settings.Settings.Llm.RetentionDays == 30);
ApplyOverlayRetentionButtonState(BtnOverlayRetention90, _settings.Settings.Llm.RetentionDays == 90);
ApplyOverlayRetentionButtonState(BtnOverlayRetentionUnlimited, _settings.Settings.Llm.RetentionDays == 0);
}
private void ApplyOverlayRetentionButtonState(Button? button, bool selected)
{
if (button == null)
return;
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
var border = TryFindResource("BorderColor") as Brush ?? Brushes.LightGray;
var hint = TryFindResource("HintBackground") as Brush ?? Brushes.Transparent;
var primary = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
var secondary = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
button.Background = selected ? hint : Brushes.Transparent;
button.BorderBrush = selected ? accent : border;
button.BorderThickness = new Thickness(1);
button.Foreground = selected ? accent : primary;
button.FontWeight = selected ? FontWeights.SemiBold : FontWeights.Normal;
button.Cursor = Cursors.Hand;
}
private void RefreshOverlayStorageSummary()
{
if (OverlayStorageSummaryText == null || OverlayStorageDriveText == null)
return;
var report = StorageAnalyzer.Analyze();
var appTotal = report.Conversations + report.AuditLogs + report.Logs + report.CodeIndex + report.EmbeddingDb + report.ClipboardHistory + report.Plugins + report.Skills + report.Settings;
OverlayStorageSummaryText.Text = $"앱 전체 사용량: {FormatStorageBytes(appTotal)}";
if (!string.IsNullOrWhiteSpace(report.DriveLabel) && report.DriveTotalSpace > 0)
{
var used = report.DriveTotalSpace - report.DriveFreeSpace;
var percent = report.DriveTotalSpace == 0 ? 0 : (int)Math.Round((double)used / report.DriveTotalSpace * 100);
OverlayStorageDriveText.Text = $"{report.DriveLabel} · 사용 {percent}% · 여유 {FormatStorageBytes(report.DriveFreeSpace)}";
}
else
{
OverlayStorageDriveText.Text = "로컬 앱 데이터 폴더 기준 사용량입니다.";
}
}
private void BtnOverlayRetention_Click(object sender, RoutedEventArgs e)
{
if (sender is not FrameworkElement element)
return;
var retainDays = element.Name switch
{
"BtnOverlayRetention7" => 7,
"BtnOverlayRetention30" => 30,
"BtnOverlayRetention90" => 90,
"BtnOverlayRetentionUnlimited" => 0,
_ => _settings.Settings.Llm.RetentionDays
};
_settings.Settings.Llm.RetentionDays = retainDays;
PersistOverlaySettingsState(refreshOverlayDeferredInputs: false);
RefreshOverlayRetentionButtons();
}
private void BtnOverlayStorageRefresh_Click(object sender, RoutedEventArgs e)
{
RefreshOverlayStorageSummary();
}
private void BtnOverlayDeleteAllConversations_Click(object sender, RoutedEventArgs e)
{
BtnDeleteAll_Click(sender, e);
RefreshOverlayStorageSummary();
}
private void BtnOverlayStorageCleanup_Click(object sender, RoutedEventArgs e)
{
var retainDays = Math.Max(0, _settings.Settings.Llm.RetentionDays);
var cleanedBytes = StorageAnalyzer.Cleanup(
retainDays,
cleanConversations: false,
cleanAuditLogs: true,
cleanLogs: true,
cleanCodeIndex: true,
cleanClipboard: true);
RefreshOverlayStorageSummary();
CustomMessageBox.Show(
cleanedBytes > 0
? $"저장 공간을 정리했습니다.\n확보된 공간: {StorageAnalyzer.FormatSize(cleanedBytes)}"
: "정리할 항목이 없었습니다.",
"저장 공간 정리",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
private static string FormatStorageBytes(long bytes)
{
if (bytes >= 1024L * 1024 * 1024)
return $"{bytes / 1024.0 / 1024 / 1024:F1} GB";
if (bytes >= 1024L * 1024)
return $"{bytes / 1024.0 / 1024:F1} MB";
if (bytes >= 1024L)
return $"{bytes / 1024.0:F0} KB";
return $"{bytes} B";
}
private void RefreshOverlayEtcPanels() private void RefreshOverlayEtcPanels()
{ {
var llm = _settings.Settings.Llm; var llm = _settings.Settings.Llm;
@@ -16993,6 +17051,12 @@ public partial class ChatWindow : Window
private void BtnOverlayFolderDataUsage_Click(object sender, RoutedEventArgs e) private void BtnOverlayFolderDataUsage_Click(object sender, RoutedEventArgs e)
{ {
if (IsFolderDataAlwaysEnabledTab())
{
_folderDataUsage = "active";
PersistOverlaySettingsState(refreshOverlayDeferredInputs: false);
return;
}
_folderDataUsage = _folderDataUsage switch _folderDataUsage = _folderDataUsage switch
{ {
"none" => "passive", "none" => "passive",
@@ -17077,7 +17141,7 @@ public partial class ChatWindow : Window
if (_isOverlaySettingsSyncing || CmbOverlayFolderDataUsage.SelectedItem is not ComboBoxItem selected || selected.Tag is not string tag) if (_isOverlaySettingsSyncing || CmbOverlayFolderDataUsage.SelectedItem is not ComboBoxItem selected || selected.Tag is not string tag)
return; return;
_folderDataUsage = tag; _folderDataUsage = IsFolderDataAlwaysEnabledTab() ? "active" : tag;
PersistOverlaySettingsState(refreshOverlayDeferredInputs: false); PersistOverlaySettingsState(refreshOverlayDeferredInputs: false);
} }
@@ -19234,7 +19298,7 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
} }
if (GitBranchLabel != null) if (GitBranchLabel != null)
GitBranchLabel.Text = string.IsNullOrWhiteSpace(branchName) ? "브랜치 없음" : $"브랜치 {branchName}"; GitBranchLabel.Text = string.IsNullOrWhiteSpace(branchName) ? "브랜치 없음" : branchName;
if (GitBranchFilesText != null) if (GitBranchFilesText != null)
GitBranchFilesText.Text = filesText; GitBranchFilesText.Text = filesText;
if (GitBranchAddedText != null) if (GitBranchAddedText != null)