[Phase 17-UI-C/D/E] AgentSessionHeaderBar·AgentSidebarView·AgentInputArea UserControl 통합

Phase 17-UI-C: AgentSessionHeaderBar 통합 (ChatWindow.xaml 인라인 칩 교체)
- AgentSessionHeaderBar.xaml: 외부 Border 투명화, 높이 42→38px, PlanIcon/PermIcon x:Name 추가
- AgentSessionHeaderBar.xaml.cs: SetPlanMode() x:Name 직접 참조, ChipPermission_Click 이벤트 위임
- ChatWindow.xaml: ModelHeaderChip/BtnPlanMode/PermissionHeaderChip → SessionHeaderBar UserControl 교체
- ChatWindow.SessionHeaderBar.cs (신규, 46줄): InitSessionHeaderBar() 이벤트 구독
- ChatWindow.ModelSelector.cs: UpdateModelLabel() SessionHeaderBar.SetModel() 동기화
- ChatWindow.PermissionMenu.cs: UpdatePermissionUI() SessionHeaderBar.SetPermissionMode() 동기화
- ChatWindow.TabSwitching.cs: UpdatePlanModeUI() 단순화, UpdateTabUI() SetTabLabel() 동기화

Phase 17-UI-D: AgentSidebarView 통합 (SidebarPanel 인라인 110줄 → UserControl 1줄)
- AgentSidebarView.xaml: 7행 구조(헤더/탭세그먼트/검색/카테고리/대화목록/삭제/사용자계정) 재작성
- AgentSidebarView.xaml.cs: internal 프록시 프로퍼티 8개 + 5개 이벤트 추가, 완전 재작성
- ChatWindow.xaml: <Border x:Name="SidebarPanel"> 110줄 → <ctrl:AgentSidebarView x:Name="Sidebar"/> 1줄
- ChatWindow.SidebarCompat.cs (신규, 80줄): 프록시 계산 프로퍼티 8개 + InitSidebarEvents()
- ChatWindow.PermissionMenu.cs: SidebarPanel.Visibility → Sidebar.Visibility, PermissionHeaderChip → SessionHeaderBar
- ChatWindow.TabSwitching.cs: 탭 핸들러 3개 Sidebar?.SetActiveTab() 동기화 추가

Phase 17-UI-E: AgentInputArea IsToolbarOnly 모드 + 입력 툴바 추가
- AgentInputArea.xaml: OuterBorder/InputTextBoxRow/ChipsSendRow x:Name 추가
- AgentInputArea.xaml.cs: IsToolbarOnly 의존성 프로퍼티 + ApplyToolbarOnlyMode() 추가
- ChatWindow.xaml: InputBorder Grid에 Row 0 추가(AgentInputArea IsToolbarOnly=True), 기존 행 0→1/1→2/2→3 시프트
- ChatWindow.InputToolbar.cs (신규, 37줄): InitInputToolbar() — @/스킬/첨부 이벤트 기존 동작 위임
- ChatWindow.xaml.cs: Loaded에 InitSidebarEvents(), InitInputToolbar() 호출 추가

빌드: 경고 0, 오류 0
This commit is contained in:
2026-04-03 22:36:19 +09:00
parent 26c20cf3dc
commit 5fe6d5c6ba
15 changed files with 565 additions and 565 deletions

View File

@@ -4892,5 +4892,67 @@ ThemeResourceHelper에 5개 정적 필드 추가:
---
최종 업데이트: 2026-04-03 (Phase 22~52 + Phase 17-UI-A~B 구현 완료)
## Phase 17-UI-C — AgentSessionHeaderBar UserControl 통합 (v1.8.0) ✅ 완료
> **목표**: 기존 Row 1 서브 바의 모델·Plan·권한 칩을 `AgentSessionHeaderBar` UserControl로 교체.
> 투명 배경으로 기존 레이아웃에 임베드. 이벤트 기반 위임으로 ChatWindow 기존 로직 재사용.
### 변경 파일
| 파일 | 변경 내용 |
|------|----------|
| `AgentSessionHeaderBar.xaml` | 외부 Border를 `Background="Transparent" BorderThickness="0"`으로 수정. 높이 42→38px. `x:Name="PlanIcon"`, `x:Name="PermIcon"` 추가. |
| `AgentSessionHeaderBar.xaml.cs` | `SetPlanMode()`: `PlanIcon` x:Name 직접 참조. `ChipPermission_Click`: 이벤트 발행만 (팝업은 ChatWindow 처리). `SetPermissionMode()`: PermIcon 색상 업데이트. |
| `ChatWindow.xaml` | Row 1 우측에서 `ModelHeaderChip`, `BtnPlanMode`, `PermissionHeaderChip` 제거 → `<ctrl:AgentSessionHeaderBar x:Name="SessionHeaderBar"/>` 추가. |
| `ChatWindow.SessionHeaderBar.cs` | 신규: `InitSessionHeaderBar()` — ModelChipClicked·PlanModeChanged·PermissionModeChanged·SettingsRequested 이벤트 구독. |
| `ChatWindow.ModelSelector.cs` | `UpdateModelLabel()`: `SessionHeaderBar?.SetModel()` 동기화. |
| `ChatWindow.PermissionMenu.cs` | `UpdatePermissionUI()`: `SessionHeaderBar?.SetPermissionMode()` 동기화. |
| `ChatWindow.TabSwitching.cs` | `UpdateTabUI()`: `SessionHeaderBar?.SetTabLabel()` 동기화. `UpdatePlanModeUI()`: SessionHeaderBar 전용으로 단순화. |
| `ChatWindow.xaml.cs` | Loaded: `InitSessionHeaderBar()` 호출 추가. |
- **빌드**: 경고 0, 오류 0
---
## Phase 17-UI-D — AgentSidebarView UserControl 통합 (v1.8.0) ✅ 완료
> **목표**: 기존 `SidebarPanel` Border(인라인 XAML 110줄)를 `AgentSidebarView` UserControl로 교체.
> 34개 기존 ChatWindow 파셜 파일이 사이드바 내부 요소를 무수정으로 참조할 수 있도록 프록시 패턴 적용.
### 변경 파일
| 파일 | 변경 내용 |
|------|----------|
| `AgentSidebarView.xaml` | 7행 구조로 재작성: 헤더(로고+새대화) / 탭세그먼트 / 검색 / 카테고리드롭다운 / 대화목록 / 삭제 / 사용자계정. |
| `AgentSidebarView.xaml.cs` | 완전 재작성: `internal` 프록시 프로퍼티 8개 + `SidebarTabChanged`/`NewChatRequested`/`DeleteAllRequested`/`CategoryDropClicked`/`SearchTextChanged` 이벤트. |
| `ChatWindow.xaml` | `<Border x:Name="SidebarPanel">` 인라인 110줄 → `<ctrl:AgentSidebarView x:Name="Sidebar"/>` 1줄 대체. |
| `ChatWindow.SidebarCompat.cs` | 신규: 8개 계산 프로퍼티(`ConversationPanel`, `SearchBox`, `CategoryIcon`, `CategoryLabel`, `BtnCategoryDrop`, `UserInitialSidebar`, `UserNameText`, `UserPcText`) + `InitSidebarEvents()`. |
| `ChatWindow.PermissionMenu.cs` | `BtnToggleSidebar_Click()`: `SidebarPanel.Visibility``Sidebar.Visibility`. `PermissionHeaderChip_Click()`: `PermissionHeaderChip``SessionHeaderBar`. |
| `ChatWindow.TabSwitching.cs` | 탭 핸들러 3개: `Sidebar?.SetActiveTab()` 동기화 추가. `UpdatePlanModeUI()`: 죽은 코드 제거. |
| `ChatWindow.xaml.cs` | Loaded: `InitSidebarEvents()` 호출 추가. |
- **빌드**: 경고 0, 오류 0
---
## Phase 17-UI-E — AgentInputArea 툴바 통합 (v1.8.0) ✅ 완료
> **목표**: `AgentInputArea` UserControl에 `IsToolbarOnly` 모드 추가.
> 기존 InputBox 위에 @ / 스킬 / 첨부 툴바 행을 추가하여 Claude.ai 스타일 입력 경험 제공.
### 변경 파일
| 파일 | 변경 내용 |
|------|----------|
| `AgentInputArea.xaml` | `x:Name="OuterBorder"`, `x:Name="InputTextBoxRow"`, `x:Name="ChipsSendRow"` 추가. |
| `AgentInputArea.xaml.cs` | `IsToolbarOnly` 의존성 프로퍼티 추가. `ApplyToolbarOnlyMode()`: TextBox/칩 행 Collapsed, 외부 Border 투명화. |
| `ChatWindow.xaml` | InputBorder Grid에 Row 0 추가(`ctrl:AgentInputArea x:Name="InputToolbar" IsToolbarOnly="True"`). 기존 Row 0→1, 1→2, 2→3 시프트. |
| `ChatWindow.InputToolbar.cs` | 신규: `InitInputToolbar()` — MentionRequested(@삽입), SkillRequested(/삽입), AttachRequested(BtnAttach_Click 위임). |
| `ChatWindow.xaml.cs` | Loaded: `InitInputToolbar()` 호출 추가. |
- **빌드**: 경고 0, 오류 0
---
최종 업데이트: 2026-04-03 (Phase 22~52 + Phase 17-UI-A~E 구현 완료)

View File

@@ -0,0 +1,37 @@
using System.Windows;
namespace AxCopilot.Views;
/// <summary>
/// Phase 17-UI-E: AgentInputArea InputToolbar 이벤트 연결.
/// @ 멘션, / 스킬, 📎 첨부 버튼을 기존 ChatWindow 동작에 위임합니다.
/// </summary>
public partial class ChatWindow
{
private void InitInputToolbar()
{
if (InputToolbar == null) return;
// @ 멘션 클릭 → InputBox에 "@" 삽입 후 포커스
InputToolbar.MentionRequested += (_, _) =>
{
InputBox.Text += "@";
InputBox.CaretIndex = InputBox.Text.Length;
InputBox.Focus();
};
// / 스킬 클릭 → InputBox에 "/" 삽입 후 포커스 (슬래시 팝업 트리거)
InputToolbar.SkillRequested += (_, _) =>
{
InputBox.Text += "/";
InputBox.CaretIndex = InputBox.Text.Length;
InputBox.Focus();
};
// 첨부 클릭 → 기존 파일 첨부 다이얼로그 (BtnAttach_Click 대응)
InputToolbar.AttachRequested += (_, _) =>
{
BtnAttach_Click(InputToolbar, new RoutedEventArgs());
};
}
}

View File

@@ -141,9 +141,8 @@ public partial class ChatWindow
};
var modelName = GetCurrentModelDisplayName();
ModelLabel.Text = $"{serviceLabel} · {modelName}";
// Phase 17-UI-B: 헤더 바 모델 칩도 갱신
if (ModelHeaderLabel != null)
ModelHeaderLabel.Text = $"{serviceLabel} · {modelName}";
// Phase 17-UI-C: SessionHeaderBar 모델 칩 동기화
SessionHeaderBar?.SetModel($"{serviceLabel} · {modelName}");
}
private void BtnModelSelector_Click(object sender, RoutedEventArgs e)

View File

@@ -101,12 +101,12 @@ public partial class ChatWindow
AutoPermissionWarning.Visibility = Visibility.Collapsed;
}
/// <summary>Phase 17-UI-B: 헤더 바 권한 칩 클릭 — 권한 팝업을 위치에 표시.</summary>
/// <summary>Phase 17-UI-C: SessionHeaderBar 권한 칩 클릭 — 권한 팝업을 SessionHeaderBar 위치에 표시.</summary>
private void PermissionHeaderChip_Click(object sender, RoutedEventArgs e)
{
if (PermissionPopup == null) return;
// 팝업 기준점을 헤더 칩으로 변경
PermissionPopup.PlacementTarget = PermissionHeaderChip;
// 팝업 기준점을 SessionHeaderBar로 변경 (PermissionHeaderChip → SessionHeaderBar로 대체)
PermissionPopup.PlacementTarget = SessionHeaderBar;
PermissionPopup.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
BtnPermission_Click(sender, e);
}
@@ -116,8 +116,8 @@ public partial class ChatWindow
if (PermissionLabel == null || PermissionIcon == null) return;
var perm = Llm.FilePermission;
PermissionLabel.Text = perm;
// Phase 17-UI-B: 헤더 칩 텍스트도 갱신
if (PermissionHeaderLabel != null) PermissionHeaderLabel.Text = perm;
// Phase 17-UI-C: SessionHeaderBar 권한 칩 동기화
SessionHeaderBar?.SetPermissionMode(perm);
PermissionIcon.Text = perm switch
{
"Auto" => "\uE73E",
@@ -466,7 +466,7 @@ public partial class ChatWindow
// 사이드바 열기, 아이콘 바 숨기기
IconBarColumn.Width = new GridLength(0);
IconBarPanel.Visibility = Visibility.Collapsed;
SidebarPanel.Visibility = Visibility.Visible;
Sidebar.Visibility = Visibility.Visible;
ToggleSidebarIcon.Text = "\uE76B";
AnimateSidebar(0, 270, () => SidebarColumn.MinWidth = 200);
}
@@ -477,7 +477,7 @@ public partial class ChatWindow
ToggleSidebarIcon.Text = "\uE76C";
AnimateSidebar(270, 0, () =>
{
SidebarPanel.Visibility = Visibility.Collapsed;
Sidebar.Visibility = Visibility.Collapsed;
IconBarColumn.Width = new GridLength(52);
IconBarPanel.Visibility = Visibility.Visible;
});

View File

@@ -0,0 +1,38 @@
using System.Windows;
namespace AxCopilot.Views;
/// <summary>
/// Phase 17-UI-C: AgentSessionHeaderBar 이벤트 연결 및 상태 동기화.
/// SessionHeaderBar(UserControl) ↔ ChatWindow 기존 로직 브리지.
/// </summary>
public partial class ChatWindow
{
/// <summary>Loaded 핸들러에서 호출 — SessionHeaderBar 이벤트 연결.</summary>
private void InitSessionHeaderBar()
{
// 모델 칩 클릭 → 기존 BtnModelSelector_Click 재사용
SessionHeaderBar.ModelChipClicked += (_, _) =>
BtnModelSelector_Click(SessionHeaderBar, new RoutedEventArgs());
// Plan 모드 순환 → 설정 저장 + 기존 UI 동기화
SessionHeaderBar.PlanModeChanged += (_, mode) =>
{
Llm.PlanMode = mode;
_settings.Save();
UpdatePlanModeUI(); // SessionHeaderBar.SetPlanMode()가 내부서 호출됨
};
// 권한 칩 클릭 → 권한 팝업을 SessionHeaderBar 위치에 표시
SessionHeaderBar.PermissionModeChanged += (_, _) =>
{
if (PermissionPopup == null) return;
PermissionPopup.PlacementTarget = SessionHeaderBar;
PermissionPopup.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
BtnPermission_Click(SessionHeaderBar, new RoutedEventArgs());
};
// 설정 버튼 → 기존 ToggleSettingsPanel() 재사용
SessionHeaderBar.SettingsRequested += (_, _) => ToggleSettingsPanel();
}
}

View File

@@ -0,0 +1,80 @@
using System.Windows;
using System.Windows.Controls;
using AxCopilot.Views.Controls;
namespace AxCopilot.Views;
/// <summary>
/// Phase 17-UI-D: AgentSidebarView 프록시 어댑터.
/// 34개 기존 ChatWindow 파셜 파일이 사이드바 내부 요소를 그대로 참조할 수 있도록
/// 계산 프로퍼티(computed property)로 위임합니다.
/// </summary>
public partial class ChatWindow
{
// ── 사이드바 내부 요소 프록시 (기존 코드 무수정 호환) ─────────────────
/// <summary>대화 목록 StackPanel — RefreshConversationList 등에서 직접 사용.</summary>
private StackPanel ConversationPanel => Sidebar.SidebarConversationPanel;
/// <summary>검색 TextBox — SearchBox_TextChanged 및 검색 필터링에서 사용.</summary>
private TextBox SearchBox => Sidebar.SidebarSearchBox;
/// <summary>카테고리 아이콘 TextBlock — UpdateCategoryLabel()에서 사용.</summary>
private TextBlock CategoryIcon => Sidebar.SidebarCategoryIcon;
/// <summary>카테고리 레이블 TextBlock — UpdateCategoryLabel()에서 사용.</summary>
private TextBlock CategoryLabel => Sidebar.SidebarCategoryLabel;
/// <summary>카테고리 드롭다운 Border — 팝업 PlacementTarget으로 사용.</summary>
private Border BtnCategoryDrop => Sidebar.SidebarCategoryDropTarget;
/// <summary>사용자 이니셜 TextBlock — SetupUserInfo()에서 사용.</summary>
private TextBlock UserInitialSidebar => Sidebar.SidebarUserInitial;
/// <summary>사용자 이름 TextBlock — SetupUserInfo()에서 사용.</summary>
private TextBlock UserNameText => Sidebar.SidebarUserName;
/// <summary>사용자 PC 텍스트 TextBlock — SetupUserInfo()에서 사용.</summary>
private TextBlock UserPcText => Sidebar.SidebarUserPc;
// ── 사이드바 이벤트 구독 초기화 ──────────────────────────────────────
private void InitSidebarEvents()
{
// 탭 전환: 사이드바 탭 클릭 → 메인 탭 RadioButton 동기화
Sidebar.SidebarTabChanged += (_, tab) =>
{
_activeTab = tab;
switch (tab)
{
case "Cowork": TabCowork.IsChecked = true; break;
case "Code": TabCode.IsChecked = true; break;
default: TabChat.IsChecked = true; break;
}
};
// 새 대화 요청
Sidebar.NewChatRequested += (_, _) =>
{
BtnNewChat_Click(Sidebar, new RoutedEventArgs());
};
// 전체 삭제 요청
Sidebar.DeleteAllRequested += (_, _) =>
{
BtnDeleteAll_Click(Sidebar, new RoutedEventArgs());
};
// 카테고리 드롭다운 클릭
Sidebar.CategoryDropClicked += (_, _) =>
{
BtnCategoryDrop_Click(Sidebar, new RoutedEventArgs());
};
// 검색어 변경 — SearchBox_TextChanged는 기존 코드가 TextBox sender로 받음
Sidebar.SearchTextChanged += (sender, e) =>
{
SearchBox_TextChanged(sender, e);
};
}
}

View File

@@ -1,6 +1,5 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using AxCopilot.Services;
namespace AxCopilot.Views;
@@ -64,6 +63,7 @@ public partial class ChatWindow
SaveCurrentTabConversationId();
_activeTab = "Chat";
_selectedCategory = ""; UpdateCategoryLabel();
Sidebar?.SetActiveTab("Chat"); // Phase 17-UI-D: 사이드바 탭 하이라이트 동기화
UpdateTabUI();
UpdatePlanModeUI();
}
@@ -75,6 +75,7 @@ public partial class ChatWindow
SaveCurrentTabConversationId();
_activeTab = "Cowork";
_selectedCategory = ""; UpdateCategoryLabel();
Sidebar?.SetActiveTab("Cowork"); // Phase 17-UI-D: 사이드바 탭 하이라이트 동기화
UpdateTabUI();
UpdatePlanModeUI();
}
@@ -86,6 +87,7 @@ public partial class ChatWindow
SaveCurrentTabConversationId();
_activeTab = "Code";
_selectedCategory = ""; UpdateCategoryLabel();
Sidebar?.SetActiveTab("Code"); // Phase 17-UI-D: 사이드바 탭 하이라이트 동기화
UpdateTabUI();
UpdatePlanModeUI();
}
@@ -143,6 +145,9 @@ public partial class ChatWindow
// 현재 대화를 해당 탭 대화로 전환
SwitchToTabConversation();
// Phase 17-UI-C: SessionHeaderBar 탭 레이블 동기화
SessionHeaderBar?.SetTabLabel(_activeTab);
// Phase 17-UI: 설정 패널이 열려 있으면 탭 전환 시 패널 업데이트
if (SettingsPanel?.IsOpen == true)
SettingsPanel.UpdateActiveTab(_activeTab);
@@ -167,23 +172,8 @@ public partial class ChatWindow
private void UpdatePlanModeUI()
{
var planMode = Llm.PlanMode ?? "off";
if (PlanModeValue == null) return;
PlanModeValue.Text = planMode switch
{
"auto" => "Auto",
"always" => "Always",
_ => "Off"
};
var isActive = planMode != "off";
var activeBrush = ThemeResourceHelper.Accent(this);
var secondaryBrush = ThemeResourceHelper.Secondary(this);
if (PlanModeIcon != null) PlanModeIcon.Foreground = isActive ? activeBrush : secondaryBrush;
if (PlanModeLabel != null) PlanModeLabel.Foreground = isActive ? activeBrush : secondaryBrush;
if (BtnPlanMode != null)
BtnPlanMode.Background = isActive
? new SolidColorBrush(Color.FromArgb(0x1A, 0x4B, 0x5E, 0xFC))
: Brushes.Transparent;
// Phase 17-UI-C: SessionHeaderBar가 Plan 칩을 완전히 대체함
SessionHeaderBar?.SetPlanMode(planMode);
}
private void SwitchToTabConversation()

View File

@@ -185,121 +185,9 @@
</Border>
<!-- ══════════════════════════════════════════════════════ -->
<!-- 좌측: 사이드바 -->
<!-- 좌측: 사이드바 (Phase 17-UI-D: AgentSidebarView UserControl) -->
<!-- ══════════════════════════════════════════════════════ -->
<Border x:Name="SidebarPanel" Grid.Column="1"
Background="{DynamicResource ItemBackground}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="54"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="52"/>
</Grid.RowDefinitions>
<!-- 헤더: 로고 + 새 대화 -->
<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"
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="AX Agent" FontSize="14" FontWeight="Bold"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center" Margin="10,0,0,0"/>
</StackPanel>
<Button x:Name="BtnNewChat" Style="{StaticResource GhostBtn}"
HorizontalAlignment="Right" VerticalAlignment="Center"
Click="BtnNewChat_Click" ToolTip="새 대화 (Ctrl+N)"
WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Text="&#xE710;" FontFamily="Segoe MDL2 Assets" FontSize="14"
Foreground="{DynamicResource SecondaryText}"/>
</Button>
</Grid>
<!-- 검색 -->
<Border Grid.Row="1" Background="{DynamicResource HintBackground}"
CornerRadius="10" Margin="12,0,12,8" Padding="10,7">
<Grid>
<TextBlock Text="&#xE721;" FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="{DynamicResource SecondaryText}" VerticalAlignment="Center"/>
<TextBox x:Name="SearchBox" Background="Transparent" BorderThickness="0"
Foreground="{DynamicResource PrimaryText}"
CaretBrush="{DynamicResource AccentColor}" FontSize="12"
VerticalAlignment="Center" Margin="22,0,0,0"
TextChanged="SearchBox_TextChanged"/>
</Grid>
</Border>
<!-- 카테고리 드롭다운: "모든 주제 ▼" -->
<Border Grid.Row="2" Margin="12,0,12,6">
<Button x:Name="BtnCategoryDrop" Style="{StaticResource GhostBtn}"
HorizontalAlignment="Stretch" Padding="10,6"
Click="BtnCategoryDrop_Click">
<Grid HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="CategoryIcon" Text="&#xE8BD;"
FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="{DynamicResource AccentColor}"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBlock x:Name="CategoryLabel" Text="모든 주제"
FontSize="12" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Text="&#xE70D;" FontFamily="Segoe MDL2 Assets" FontSize="9"
Foreground="{DynamicResource SecondaryText}"
HorizontalAlignment="Right" VerticalAlignment="Center"/>
</Grid>
</Button>
</Border>
<!-- 대화 목록 -->
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="ConversationPanel" Margin="6,0"/>
</ScrollViewer>
<!-- 하단: 삭제 -->
<Border Grid.Row="4" BorderBrush="{DynamicResource SeparatorColor}" BorderThickness="0,1,0,0"
Padding="0,4">
<Button x:Name="BtnDeleteAll" Style="{StaticResource GhostBtn}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="BtnDeleteAll_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE74D;" FontFamily="Segoe MDL2 Assets" FontSize="11"
Foreground="#AA5555" VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBlock Text="전체 삭제" FontSize="11" Foreground="#AA5555"/>
</StackPanel>
</Button>
</Border>
<!-- 하단: 사용자 계정 -->
<Border Grid.Row="5" Margin="12,0,12,8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Width="34" Height="34" CornerRadius="17"
Background="{DynamicResource AccentColor}" Margin="0,0,10,0">
<TextBlock x:Name="UserInitialSidebar" Text="U" FontSize="14" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock x:Name="UserNameText" Text="" FontSize="12" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"
TextTrimming="CharacterEllipsis"/>
<TextBlock x:Name="UserPcText" Text="" FontSize="10"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</Border>
<ctrl:AgentSidebarView x:Name="Sidebar" Grid.Column="1"/>
<!-- ══════════════════════════════════════════════════════ -->
<!-- 우측: 메시지 영역 -->
@@ -340,52 +228,10 @@
KeyDown="ChatTitleEdit_KeyDown"/>
</Grid>
</StackPanel>
<!-- 우: 모델 칩 + Plan 모드 + 권한 칩 + 프리뷰 토글 버튼 -->
<!-- 우: AgentSessionHeaderBar (모델/Plan/권한 칩) + 미리보기 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<!-- Phase 17-UI-B: 모델 표시 칩 (클릭 → 모델 선택기) -->
<Button x:Name="ModelHeaderChip" Style="{StaticResource OutlineHoverBtn}"
Click="BtnModelSelector_Click"
ToolTip="모델 변경" Margin="0,0,4,0" Padding="7,3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xEA86;" FontFamily="Segoe MDL2 Assets" FontSize="10"
Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center" Margin="0,0,4,0"/>
<TextBlock x:Name="ModelHeaderLabel" Text="" FontSize="11"
Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- Plan 모드 토글 (Off/Auto/Always 3단 순환 클릭) -->
<Border x:Name="BtnPlanMode" Cursor="Hand"
CornerRadius="6" Padding="6,3"
Background="#1A4B5EFC" Margin="0,0,6,0"
MouseLeftButtonUp="BtnPlanMode_Click"
ToolTip="Plan 모드: 에이전트가 실행 전 계획을 수립합니다">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="PlanModeIcon" Text="&#xE9D9;" FontFamily="Segoe MDL2 Assets" FontSize="11"
Foreground="{DynamicResource AccentColor}"
VerticalAlignment="Center" Margin="0,0,4,0"/>
<TextBlock x:Name="PlanModeLabel" Text="Plan" FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource AccentColor}"
VerticalAlignment="Center"/>
<TextBlock x:Name="PlanModeValue" Text="Off" FontSize="11"
Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center" Margin="4,0,0,0"/>
</StackPanel>
</Border>
<!-- Phase 17-UI-B: 권한 모드 표시 칩 (클릭 → 권한 팝업) -->
<Button x:Name="PermissionHeaderChip" Style="{StaticResource OutlineHoverBtn}"
Click="PermissionHeaderChip_Click"
ToolTip="파일 접근 권한 모드 변경" Margin="0,0,6,0" Padding="7,3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE72E;" FontFamily="Segoe MDL2 Assets" FontSize="10"
Foreground="#4FC3F7"
VerticalAlignment="Center" Margin="0,0,4,0"/>
<TextBlock x:Name="PermissionHeaderLabel" Text="Ask" FontSize="11"
Foreground="{DynamicResource SecondaryText}"
VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- Phase 17-UI-C: AgentSessionHeaderBar UserControl -->
<ctrl:AgentSessionHeaderBar x:Name="SessionHeaderBar" VerticalAlignment="Center"/>
<Button x:Name="BtnPreviewToggle" Style="{StaticResource GhostBtn}"
Click="BtnPreviewToggle_Click" ToolTip="미리보기 패널" Visibility="Collapsed"
Padding="8,4">
@@ -767,13 +613,18 @@
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1.5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Row 0: 모델 셀렉터 + 템플릿 버튼 -->
<Grid Grid.Row="0">
<!-- Row 0: Phase 17-UI-E: @ / 스킬 / 첨부 툴바 (AgentInputArea 툴바 전용) -->
<ctrl:AgentInputArea x:Name="InputToolbar" Grid.Row="0"
IsToolbarOnly="True"/>
<!-- Row 1: 모델 셀렉터 + 템플릿 버튼 -->
<Grid Grid.Row="1">
<Button x:Name="BtnModelSelector"
Style="{StaticResource OutlineHoverBtn}"
HorizontalAlignment="Left"
@@ -811,8 +662,8 @@
</Button>
</Grid>
<!-- Row 1: 첨부 파일 목록 -->
<ItemsControl x:Name="AttachedFilesPanel" Grid.Row="1"
<!-- Row 2: 첨부 파일 목록 -->
<ItemsControl x:Name="AttachedFilesPanel" Grid.Row="2"
Margin="8,0,8,2" Visibility="Collapsed">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
@@ -821,8 +672,8 @@
</ItemsControl.ItemsPanel>
</ItemsControl>
<!-- 입력 영역 (Row 2) -->
<Grid Grid.Row="2">
<!-- 입력 영역 (Row 3) -->
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>

View File

@@ -127,7 +127,10 @@ public partial class ChatWindow : Window
UpdateAnalyzerButtonVisibility();
UpdateModelLabel();
UpdatePlanModeUI();
UpdatePermissionUI(); // Phase 17-UI-B: 헤더 권한 칩 초기화
InitSessionHeaderBar(); // Phase 17-UI-C: 이벤트 연결
InitSidebarEvents(); // Phase 17-UI-D: 사이드바 이벤트 연결
InitInputToolbar(); // Phase 17-UI-E: 입력 툴바 이벤트 연결
UpdatePermissionUI(); // Phase 17-UI-C: 헤더 권한 칩 초기화
InputBox.Focus();
MessageScroll.ScrollChanged += MessageScroll_ScrollChanged;

View File

@@ -2,10 +2,12 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Phase 32-D: 멀티기능 입력 영역 (Claude.ai + Codex 스타일)
상단 툴바 (@멘션/스킬/첨부) + 텍스트 입력 + 하단 칩/전송 -->
<!-- Phase 17-UI-E: 멀티기능 입력 영역 (Claude.ai + Codex 스타일)
IsToolbarOnly=True 시 Row 0 (@ / 스킬 / 첨부)만 표시.
ChatWindow에서는 툴바 행만 사용하여 기존 InputBox 위에 배치. -->
<Border Background="{DynamicResource LauncherBackground}"
<Border x:Name="OuterBorder"
Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="0,1,0,0"
Padding="16,8">
<Grid>
@@ -15,7 +17,7 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Row 0: 상단 툴바 — @ / 스킬 / 첨부 -->
<!-- Row 0: 상단 툴바 — @ / 스킬 / 첨부 (IsToolbarOnly=True에서도 표시) -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,6">
<!-- @ 멘션 -->
<Border CornerRadius="4" Padding="6,3" Margin="0,0,6,0" Cursor="Hand"
@@ -74,8 +76,9 @@
<StackPanel x:Name="AttachmentChips" Orientation="Horizontal" Margin="8,0,0,0"/>
</StackPanel>
<!-- Row 1: 텍스트 입력 -->
<Border Grid.Row="1" Background="{DynamicResource ItemBackground}"
<!-- Row 1: 텍스트 입력 (IsToolbarOnly=True 시 Collapsed) -->
<Border x:Name="InputTextBoxRow" Grid.Row="1"
Background="{DynamicResource ItemBackground}"
CornerRadius="12" Padding="12,8" Margin="0,0,0,6">
<TextBox x:Name="InputBox" FontSize="13" AcceptsReturn="True"
TextWrapping="Wrap" MinHeight="40" MaxHeight="200"
@@ -87,8 +90,8 @@
</TextBox>
</Border>
<!-- Row 2: 하단 칩 + 전송 버튼 -->
<Grid Grid.Row="2">
<!-- Row 2: 하단 칩 + 전송 버튼 (IsToolbarOnly=True 시 Collapsed) -->
<Grid x:Name="ChipsSendRow" Grid.Row="2">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<!-- 모델명 칩 -->
<Border CornerRadius="10" Padding="8,3" Margin="0,0,6,0"

View File

@@ -5,13 +5,56 @@ using System.Windows.Input;
namespace AxCopilot.Views.Controls;
/// <summary>
/// Phase 32-D: 멀티기능 입력 영역.
/// Phase 17-UI-E: 멀티기능 입력 영역.
/// @ 멘션, / 스킬, 첨부, Ctrl+Enter 전송, 모델/권한/Plan 칩 표시.
/// IsToolbarOnly=True 시 상단 툴바 행(@/스킬/첨부)만 표시하고
/// TextBox 행과 칩/전송 행은 숨깁니다 (ChatWindow 기존 InputBox 위에 배치용).
/// </summary>
public partial class AgentInputArea : UserControl
{
private bool _isStreaming;
// ── IsToolbarOnly 의존성 프로퍼티 ─────────────────────────────────────
public static readonly DependencyProperty IsToolbarOnlyProperty =
DependencyProperty.Register(
nameof(IsToolbarOnly),
typeof(bool),
typeof(AgentInputArea),
new PropertyMetadata(false, OnIsToolbarOnlyChanged));
/// <summary>True이면 @/스킬/첨부 툴바 행만 표시합니다.</summary>
public bool IsToolbarOnly
{
get => (bool)GetValue(IsToolbarOnlyProperty);
set => SetValue(IsToolbarOnlyProperty, value);
}
private static void OnIsToolbarOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AgentInputArea ctrl)
ctrl.ApplyToolbarOnlyMode((bool)e.NewValue);
}
private void ApplyToolbarOnlyMode(bool toolbarOnly)
{
var vis = toolbarOnly ? Visibility.Collapsed : Visibility.Visible;
if (InputTextBoxRow != null) InputTextBoxRow.Visibility = vis;
if (ChipsSendRow != null) ChipsSendRow.Visibility = vis;
// 툴바 전용 모드에서는 외부 Border 배경/테두리를 투명하게 처리
if (OuterBorder != null)
{
OuterBorder.BorderThickness = toolbarOnly ? new Thickness(0) : new Thickness(0, 1, 0, 0);
OuterBorder.Padding = toolbarOnly ? new Thickness(8, 0, 8, 0) : new Thickness(16, 8, 16, 8);
OuterBorder.Background = toolbarOnly
? System.Windows.Media.Brushes.Transparent
: TryFindResource("LauncherBackground") as System.Windows.Media.Brush
?? System.Windows.Media.Brushes.Transparent;
}
}
// ── 이벤트 ────────────────────────────────────────────────────────────
/// <summary>메시지 전송 이벤트. 인자: 입력 텍스트.</summary>
public event EventHandler<string>? MessageSent;
@@ -27,6 +70,8 @@ public partial class AgentInputArea : UserControl
/// <summary>첨부 클릭 이벤트.</summary>
public event EventHandler? AttachRequested;
// ── 공개 API ──────────────────────────────────────────────────────────
/// <summary>입력 텍스트.</summary>
public string Text
{
@@ -48,6 +93,11 @@ public partial class AgentInputArea : UserControl
public AgentInputArea()
{
InitializeComponent();
// Loaded 후 IsToolbarOnly 초기 적용 (XAML 설정값 반영)
Loaded += (_, _) =>
{
if (IsToolbarOnly) ApplyToolbarOnlyMode(true);
};
}
/// <summary>하단 칩 텍스트를 업데이트합니다.</summary>
@@ -105,7 +155,7 @@ public partial class AgentInputArea : UserControl
/// <summary>입력 포커스를 설정합니다.</summary>
public void FocusInput() => InputBox.Focus();
#region
// ── 이벤트 핸들러 ───────────────────────────────────────────────────
private void InputBox_KeyDown(object sender, KeyEventArgs e)
{
@@ -139,7 +189,7 @@ public partial class AgentInputArea : UserControl
private void BtnAttach_Click(object sender, MouseButtonEventArgs e)
=> AttachRequested?.Invoke(this, EventArgs.Empty);
#endregion
// ── 내부 헬퍼 ────────────────────────────────────────────────────────
private void TrySend()
{

View File

@@ -1,26 +1,25 @@
<UserControl x:Class="AxCopilot.Views.Controls.AgentSessionHeaderBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="42">
Height="38">
<!-- Phase 32-C: 세션 헤더 바 (Codex 스타일)
모델/Plan/권한 칩 + 도구 아이콘 + 설정 버튼 -->
<!-- Phase 17-UI-C: 세션 헤더 바 — ChatWindow Row 1 에 임베드.
배경/테두리는 부모(Row 1 Border)가 담당하므로 이 컨트롤은 투명. -->
<Border Background="{DynamicResource LauncherBackground}"
BorderBrush="{DynamicResource BorderColor}" BorderThickness="0,0,0,1">
<Grid Margin="16,0">
<Border Background="Transparent" BorderThickness="0">
<Grid Margin="4,0">
<!-- 좌측: 현재 탭 레이블 -->
<!-- 좌측: 현재 탭 레이블 (탭 이름 또는 대화 제목) -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock x:Name="TxtTabLabel" Text="Chat" FontSize="14" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock x:Name="TxtTabLabel" Text="Chat" FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource SecondaryText}" Margin="4,0,8,0"/>
</StackPanel>
<!-- 중앙: 모델/Plan/권한 칩 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- 모델 칩 -->
<Border x:Name="ChipModel" CornerRadius="12" Padding="10,4" Margin="4,0"
<Border x:Name="ChipModel" CornerRadius="10" Padding="8,3" Margin="3,0"
Cursor="Hand" MouseLeftButtonUp="ChipModel_Click">
<Border.Style>
<Style TargetType="Border">
@@ -33,9 +32,9 @@
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE964;" FontFamily="Segoe MDL2 Assets" FontSize="10"
<TextBlock Text="&#xEA86;" FontFamily="Segoe MDL2 Assets" FontSize="10"
Foreground="{DynamicResource AccentColor}" VerticalAlignment="Center"/>
<TextBlock x:Name="TxtModelChip" Text="llama3" FontSize="11" Margin="4,0"
<TextBlock x:Name="TxtModelChip" Text="llama3" FontSize="11" Margin="4,0,2,0"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="&#xE70D;" FontFamily="Segoe MDL2 Assets" FontSize="8"
Foreground="{DynamicResource SecondaryText}" VerticalAlignment="Center"/>
@@ -43,7 +42,7 @@
</Border>
<!-- Plan 모드 칩 (3-state: Off/Auto/Always) -->
<Border x:Name="ChipPlan" CornerRadius="12" Padding="10,4" Margin="4,0"
<Border x:Name="ChipPlan" CornerRadius="10" Padding="8,3" Margin="3,0"
Cursor="Hand" MouseLeftButtonUp="ChipPlan_Click">
<Border.Style>
<Style TargetType="Border">
@@ -56,15 +55,15 @@
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE8FD;" FontFamily="Segoe MDL2 Assets" FontSize="10"
<TextBlock x:Name="PlanIcon" Text="&#xE9D9;" FontFamily="Segoe MDL2 Assets" FontSize="10"
Foreground="{DynamicResource SecondaryText}" VerticalAlignment="Center"/>
<TextBlock x:Name="TxtPlanChip" Text="Plan: Off" FontSize="11" Margin="4,0"
<TextBlock x:Name="TxtPlanChip" Text="Plan: Off" FontSize="11" Margin="4,0,0,0"
Foreground="{DynamicResource PrimaryText}"/>
</StackPanel>
</Border>
<!-- 권한 모드 칩 -->
<Border x:Name="ChipPermission" CornerRadius="12" Padding="10,4" Margin="4,0"
<Border x:Name="ChipPermission" CornerRadius="10" Padding="8,3" Margin="3,0"
Cursor="Hand" MouseLeftButtonUp="ChipPermission_Click">
<Border.Style>
<Style TargetType="Border">
@@ -77,18 +76,19 @@
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE72E;" FontFamily="Segoe MDL2 Assets" FontSize="10"
<TextBlock x:Name="PermIcon" Text="&#xE72E;" FontFamily="Segoe MDL2 Assets" FontSize="10"
Foreground="#4FC3F7" VerticalAlignment="Center"/>
<TextBlock x:Name="TxtPermissionChip" Text="Ask" FontSize="11" Margin="4,0"
<TextBlock x:Name="TxtPermissionChip" Text="Ask" FontSize="11" Margin="4,0,0,0"
Foreground="{DynamicResource PrimaryText}"/>
</StackPanel>
</Border>
</StackPanel>
<!-- 우측: 설정 버튼 -->
<!-- 우측: 설정 패널 토글 버튼 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Border CornerRadius="4" Padding="8,4" Cursor="Hand" Margin="4,0"
MouseLeftButtonUp="BtnSettings_Click">
<Border CornerRadius="6" Padding="6,3" Cursor="Hand" Margin="0,0,4,0"
MouseLeftButtonUp="BtnSettings_Click"
ToolTip="에이전트 설정 패널">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"/>
@@ -99,7 +99,7 @@
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets" FontSize="14"
<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets" FontSize="13"
Foreground="{DynamicResource SecondaryText}"/>
</Border>
</StackPanel>

View File

@@ -5,24 +5,25 @@ using System.Windows.Media;
namespace AxCopilot.Views.Controls;
/// <summary>
/// Phase 32-C: 세션 헤더 바 (Codex 스타일).
/// 모델/Plan/권한 칩을 인라인 표시하고, 클릭 시 빠른 전환.
/// Phase 17-UI-C: 세션 헤더 바 (Codex 스타일).
/// ChatWindow Row 1 서브바에 임베드되어 모델/Plan/권한 칩을 표시.
/// 배경/테두리는 부모 컨테이너에서 제공하므로 이 컨트롤은 투명.
/// </summary>
public partial class AgentSessionHeaderBar : UserControl
{
private string _planMode = "off"; // off auto always
private string _permissionMode = "Ask"; // Ask Auto Deny → Bypass
private string _planMode = "off"; // off / auto / always
private string _permissionMode = "Ask"; // Ask / Auto / Deny
/// <summary>모델 선택 클릭 이벤트.</summary>
/// <summary>모델 클릭 이벤트 — 부모 창에서 모델 선택 팝업 열기.</summary>
public event EventHandler? ModelChipClicked;
/// <summary>Plan 모드 변경 이벤트. 인자: 새 모드 ("off"/"auto"/"always").</summary>
public event EventHandler<string>? PlanModeChanged;
/// <summary>권한 모드 변경 이벤트. 인자: 새 모드.</summary>
/// <summary>권한 모드 변경 이벤트. 인자: 새 모드 ("Ask"/"Auto"/"Deny").</summary>
public event EventHandler<string>? PermissionModeChanged;
/// <summary>설정 패널 토글 요청.</summary>
/// <summary>설정 패널 토글 요청 이벤트.</summary>
public event EventHandler? SettingsRequested;
public AgentSessionHeaderBar()
@@ -30,46 +31,64 @@ public partial class AgentSessionHeaderBar : UserControl
InitializeComponent();
}
/// <summary>탭 레이블을 업데이트합니다.</summary>
// ─── 외부에서 상태 주입 ───────────────────────────────────────────────
/// <summary>탭 레이블(좌측)을 업데이트합니다.</summary>
public void SetTabLabel(string tab) => TxtTabLabel.Text = tab;
/// <summary>모델 업데이트합니다.</summary>
/// <summary>모델 칩 텍스트를 업데이트합니다. 30자 초과 시 축약.</summary>
public void SetModel(string modelName)
{
// 긴 모델명은 축약
var display = modelName.Length > 20 ? modelName[..20] + "…" : modelName;
TxtModelChip.Text = display;
TxtModelChip.Text = modelName.Length > 30 ? modelName[..30] + "…" : modelName;
}
/// <summary>Plan 모드를 설정합니다.</summary>
/// <summary>Plan 모드 칩을 업데이트합니다. 모드: "off" / "auto" / "always".</summary>
public void SetPlanMode(string mode)
{
_planMode = mode;
TxtPlanChip.Text = mode switch
{
"auto" => "Plan: Auto",
"auto" => "Plan: Auto",
"always" => "Plan: Always",
_ => "Plan: Off",
_ => "Plan: Off",
};
// 활성 상태 색상
var icon = ChipPlan.Child is StackPanel sp && sp.Children[0] is System.Windows.Controls.TextBlock tb ? tb : null;
if (icon != null)
// 활성 상태 색상 — PlanIcon x:Name 직접 참조
if (PlanIcon != null)
{
icon.Foreground = mode != "off"
PlanIcon.Foreground = mode != "off"
? (TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue)
: (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
}
// Plan 활성 시 칩 배경 강조
if (ChipPlan != null)
{
ChipPlan.Background = mode != "off"
? new SolidColorBrush(Color.FromArgb(0x1A, 0x4B, 0x5E, 0xFC))
: (TryFindResource("ItemBackground") as Brush ?? Brushes.Transparent);
}
}
/// <summary>권한 모드를 설정합니다.</summary>
/// <summary>권한 모드 칩을 업데이트합니다. 모드: "Ask" / "Auto" / "Deny".</summary>
public void SetPermissionMode(string mode)
{
_permissionMode = mode;
TxtPermissionChip.Text = mode;
// 권한 모드별 아이콘 색상
if (PermIcon != null)
{
PermIcon.Foreground = mode switch
{
"Auto" => new SolidColorBrush(Color.FromRgb(0xDD, 0x6B, 0x20)),
"Deny" => new SolidColorBrush(Color.FromRgb(0xC5, 0x0F, 0x1F)),
_ => new SolidColorBrush(Color.FromRgb(0x4F, 0xC3, 0xF7)), // Ask = 하늘색
};
}
}
#region
// ─── 이벤트 핸들러 ──────────────────────────────────────────────────
private void ChipModel_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
=> ModelChipClicked?.Invoke(this, EventArgs.Empty);
@@ -79,30 +98,18 @@ public partial class AgentSessionHeaderBar : UserControl
// 3-state 순환: off → auto → always → off
_planMode = _planMode switch
{
"off" => "auto",
"off" => "auto",
"auto" => "always",
_ => "off",
_ => "off",
};
SetPlanMode(_planMode);
PlanModeChanged?.Invoke(this, _planMode);
}
private void ChipPermission_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// 4-state 순환: Ask → Auto → Deny → Bypass → Ask
_permissionMode = _permissionMode switch
{
"Ask" => "Auto",
"Auto" => "Deny",
"Deny" => "Bypass",
_ => "Ask",
};
SetPermissionMode(_permissionMode);
PermissionModeChanged?.Invoke(this, _permissionMode);
}
// 권한 칩 클릭 → 팝업은 부모(ChatWindow)가 처리
=> PermissionModeChanged?.Invoke(this, _permissionMode);
private void BtnSettings_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
=> SettingsRequested?.Invoke(this, EventArgs.Empty);
#endregion
}

View File

@@ -1,52 +1,50 @@
<UserControl x:Class="AxCopilot.Views.Controls.AgentSidebarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinWidth="48" Width="260">
Width="270">
<!-- Phase 32-B: 좌측 사이드바 (Claude.ai 스타일)
탭 세그먼트 + 프리셋 + 대화 이력 + 새 대화 -->
<!-- Phase 17-UI-D: 좌측 사이드바 UserControl
기존 SidebarPanel Grid를 대체. ChatWindow 프록시 패턴으로 기존 코드와 호환.
탭 세그먼트(Chat/Cowork/Code) + 카테고리 필터 + 대화 목록 + 사용자 계정. -->
<Border Background="{DynamicResource LauncherBackground}"
<Border Background="{DynamicResource ItemBackground}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="0,0,1,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="54"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="48"/>
<RowDefinition Height="54"/> <!-- 0: 헤더 (로고 + 새 대화) -->
<RowDefinition Height="Auto"/> <!-- 1: 탭 세그먼트 (Chat/Cowork/Code) -->
<RowDefinition Height="Auto"/> <!-- 2: 검색 -->
<RowDefinition Height="Auto"/> <!-- 3: 카테고리 드롭다운 -->
<RowDefinition Height="*"/> <!-- 4: 대화 목록 -->
<RowDefinition Height="Auto"/> <!-- 5: 하단 삭제 버튼 -->
<RowDefinition Height="52"/> <!-- 6: 사용자 계정 -->
</Grid.RowDefinitions>
<!-- Row 0: 헤더 — 로고 + 접기 버튼 -->
<Grid Grid.Row="0" Margin="12,0">
<!-- Row 0: 헤더 — 로고 + 새 대화 버튼 -->
<Grid Grid.Row="0" Margin="16,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xE7F8;" FontFamily="Segoe MDL2 Assets" FontSize="18"
Foreground="{DynamicResource AccentColor}" VerticalAlignment="Center"/>
<TextBlock x:Name="TxtTitle" Text=" AX Agent" FontSize="15" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}" Margin="6,0,0,0" VerticalAlignment="Center"/>
<Border Background="{DynamicResource AccentColor}" CornerRadius="6"
Width="24" Height="24">
<TextBlock Text="&#xE8BD;" FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="AX Agent" FontSize="14" FontWeight="Bold"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center" Margin="10,0,0,0"/>
</StackPanel>
<Border HorizontalAlignment="Right" VerticalAlignment="Center"
Background="Transparent" Cursor="Hand" Padding="8,6" CornerRadius="4"
MouseLeftButtonUp="BtnCollapse_Click">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#18FFFFFF"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock x:Name="IconCollapse" Text="&#xE76B;" FontFamily="Segoe MDL2 Assets"
FontSize="12" Foreground="{DynamicResource SecondaryText}"/>
</Border>
<Button x:Name="BtnNewChat" Style="{DynamicResource GhostBtn}"
HorizontalAlignment="Right" VerticalAlignment="Center"
Click="BtnNewChat_Click" ToolTip="새 대화 (Ctrl+N)"
WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Text="&#xE710;" FontFamily="Segoe MDL2 Assets" FontSize="14"
Foreground="{DynamicResource SecondaryText}"/>
</Button>
</Grid>
<!-- Row 1: 탭 세그먼트 (Chat | Cowork | Code) -->
<Border Grid.Row="1" Margin="12,0,12,8" Background="{DynamicResource ItemBackground}"
<!-- Row 1: 탭 세그먼트 (Chat | Cowork | Code) — Claude.ai 인라인 탭 -->
<Border Grid.Row="1" Margin="12,0,12,8"
Background="{DynamicResource HintBackground}"
CornerRadius="8" Padding="3">
<Grid>
<Grid.ColumnDefinitions>
@@ -54,21 +52,20 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="TabChat" Grid.Column="0" CornerRadius="6" Padding="0,7"
Cursor="Hand" MouseLeftButtonUp="TabChat_Click">
<Border x:Name="SidebarTabChat" Grid.Column="0" CornerRadius="6" Padding="0,6"
Cursor="Hand" MouseLeftButtonUp="SidebarTabChat_Click">
<TextBlock Text="Chat" FontSize="12" FontWeight="Medium"
HorizontalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="TabCowork" Grid.Column="1" CornerRadius="6" Padding="0,7"
Cursor="Hand" MouseLeftButtonUp="TabCowork_Click">
<Border x:Name="SidebarTabCowork" Grid.Column="1" CornerRadius="6" Padding="0,6"
Cursor="Hand" MouseLeftButtonUp="SidebarTabCowork_Click">
<TextBlock Text="Cowork" FontSize="12" FontWeight="Medium"
HorizontalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
</Border>
<Border x:Name="TabCode" Grid.Column="2" CornerRadius="6" Padding="0,7"
Cursor="Hand" MouseLeftButtonUp="TabCode_Click">
<Border x:Name="SidebarTabCode" Grid.Column="2" CornerRadius="6" Padding="0,6"
Cursor="Hand" MouseLeftButtonUp="SidebarTabCode_Click">
<TextBlock Text="Code" FontSize="12" FontWeight="Medium"
HorizontalAlignment="Center"
Foreground="{DynamicResource PrimaryText}"/>
@@ -76,98 +73,91 @@
</Grid>
</Border>
<!-- Row 2: 새 대화 버튼 -->
<Border Grid.Row="2" Margin="12,0,12,8" CornerRadius="8" Padding="0,9"
Cursor="Hand" MouseLeftButtonUp="BtnNewChat_Click">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="&#xE710;" FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="{DynamicResource AccentColor}" VerticalAlignment="Center"/>
<TextBlock Text=" 새 대화" FontSize="12" FontWeight="Medium"
Foreground="{DynamicResource PrimaryText}" Margin="4,0,0,0"/>
</StackPanel>
</Border>
<!-- Row 3: 검색 -->
<Border Grid.Row="3" Margin="12,0,12,8" Background="{DynamicResource ItemBackground}"
CornerRadius="6" Padding="10,6">
<!-- Row 2: 검색 -->
<Border Grid.Row="2" Background="{DynamicResource HintBackground}"
CornerRadius="10" Margin="12,0,12,8" Padding="10,7">
<Grid>
<TextBlock Text="&#xE721;" FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="{DynamicResource SecondaryText}" VerticalAlignment="Center"/>
<TextBox x:Name="TxtSearch" Margin="22,0,0,0" FontSize="12" Background="Transparent"
Foreground="{DynamicResource PrimaryText}" BorderThickness="0"
TextChanged="TxtSearch_TextChanged">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Tag" Value="대화 검색..."/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="SearchBox" Background="Transparent" BorderThickness="0"
Foreground="{DynamicResource PrimaryText}"
CaretBrush="{DynamicResource AccentColor}" FontSize="12"
VerticalAlignment="Center" Margin="22,0,0,0"
TextChanged="SearchBox_TextChanged"/>
</Grid>
</Border>
<!-- Row 4: 대화 이력 목록 -->
<ScrollViewer Grid.Row="4" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="ConversationList" Margin="8,0">
<!-- 코드비하인드에서 날짜 그룹별로 동적 생성 -->
</StackPanel>
</ScrollViewer>
<!-- Row 5: 삭제 전체 -->
<Border Grid.Row="5" Margin="12,4,12,4" Padding="0,6" Cursor="Hand"
CornerRadius="6" MouseLeftButtonUp="BtnDeleteAll_Click">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#18FFFFFF"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="&#xE74D;" FontFamily="Segoe MDL2 Assets" FontSize="11"
Foreground="#E57373" VerticalAlignment="Center"/>
<TextBlock Text=" 모두 삭제" FontSize="11" Foreground="#E57373" Margin="4,0,0,0"/>
</StackPanel>
<!-- Row 3: 카테고리 드롭다운 -->
<Border Grid.Row="3" Margin="12,0,12,6">
<Border x:Name="BtnCategoryDrop" Cursor="Hand" Padding="10,6"
CornerRadius="8" MouseLeftButtonUp="BtnCategoryDrop_Click">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="CategoryIcon" Text="&#xE8BD;"
FontFamily="Segoe MDL2 Assets" FontSize="12"
Foreground="{DynamicResource AccentColor}"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBlock x:Name="CategoryLabel" Text="모든 주제"
FontSize="12" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"
VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Text="&#xE70D;" FontFamily="Segoe MDL2 Assets" FontSize="9"
Foreground="{DynamicResource SecondaryText}"
HorizontalAlignment="Right" VerticalAlignment="Center"/>
</Grid>
</Border>
</Border>
<!-- Row 6: 하단 설정 아이콘 -->
<Border Grid.Row="6" BorderBrush="{DynamicResource BorderColor}" BorderThickness="0,1,0,0">
<Grid Margin="12,0">
<Border HorizontalAlignment="Left" VerticalAlignment="Center"
Cursor="Hand" Padding="8,6" CornerRadius="4"
MouseLeftButtonUp="BtnSettings_Click">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#18FFFFFF"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets" FontSize="14"
Foreground="{DynamicResource SecondaryText}"/>
<!-- Row 4: 대화 목록 (x:Name="ConversationPanel" — ChatWindow 프록시 대상) -->
<ScrollViewer Grid.Row="4" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="ConversationPanel" Margin="6,0"/>
</ScrollViewer>
<!-- Row 5: 전체 삭제 -->
<Border Grid.Row="5" BorderBrush="{DynamicResource SeparatorColor}" BorderThickness="0,1,0,0"
Padding="0,4">
<Button x:Name="BtnDeleteAll" Style="{DynamicResource GhostBtn}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="BtnDeleteAll_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE74D;" FontFamily="Segoe MDL2 Assets" FontSize="11"
Foreground="#AA5555" VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBlock Text="전체 삭제" FontSize="11" Foreground="#AA5555"/>
</StackPanel>
</Button>
</Border>
<!-- Row 6: 사용자 계정 (x:Name 요소들 — ChatWindow SetupUserInfo()에서 직접 접근) -->
<Border Grid.Row="6" Margin="12,0,12,8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Width="34" Height="34" CornerRadius="17"
Background="{DynamicResource AccentColor}" Margin="0,0,10,0">
<TextBlock x:Name="UserInitialSidebar" Text="U" FontSize="14" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock x:Name="UserNameText" Text="" FontSize="12" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"
TextTrimming="CharacterEllipsis"/>
<TextBlock x:Name="UserPcText" Text="" FontSize="10"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
</Grid>
</Border>
</Grid>

View File

@@ -6,37 +6,39 @@ using System.Windows.Media.Animation;
namespace AxCopilot.Views.Controls;
/// <summary>
/// Phase 32-B: 좌측 사이드바 (Claude.ai 스타일).
/// 탭 세그먼트(Chat/Cowork/Code) + 새 대화 + 검색 + 대화 이력.
/// Phase 17-UI-D: 좌측 사이드바 UserControl.
/// 기존 SidebarPanel Grid를 대체. ChatWindow 프록시 패턴으로 기존 코드와 호환.
/// 탭 세그먼트(Chat/Cowork/Code) + 카테고리 필터 + 대화 목록 + 사용자 계정.
/// </summary>
public partial class AgentSidebarView : UserControl
{
private bool _isCollapsed;
private string _activeTab = "Chat";
// ── 프록시 프로퍼티 (ChatWindow 기존 코드가 직접 참조) ─────────────────
internal StackPanel SidebarConversationPanel => ConversationPanel;
internal TextBox SidebarSearchBox => SearchBox;
internal TextBlock SidebarCategoryIcon => CategoryIcon;
internal TextBlock SidebarCategoryLabel => CategoryLabel;
internal Border SidebarCategoryDropTarget => BtnCategoryDrop;
internal TextBlock SidebarUserInitial => UserInitialSidebar;
internal TextBlock SidebarUserName => UserNameText;
internal TextBlock SidebarUserPc => UserPcText;
// ── 이벤트 (ChatWindow가 구독) ─────────────────────────────────────────
/// <summary>탭 전환 이벤트. 인자: 탭 이름 ("Chat", "Cowork", "Code").</summary>
public event EventHandler<string>? TabChanged;
public event EventHandler<string>? SidebarTabChanged;
/// <summary>새 대화 요청 이벤트.</summary>
public event EventHandler? NewChatRequested;
/// <summary>대화 선택 이벤트. 인자: 대화 ID.</summary>
public event EventHandler<string>? ConversationSelected;
/// <summary>전체 삭제 요청 이벤트.</summary>
public event EventHandler? DeleteAllRequested;
/// <summary>설정 패널 토글 요청 이벤트.</summary>
public event EventHandler? SettingsRequested;
/// <summary>카테고리 드롭다운 클릭 이벤트.</summary>
public event EventHandler? CategoryDropClicked;
/// <summary>접기/펼치기 토글 이벤트.</summary>
public event EventHandler<bool>? CollapseToggled;
/// <summary>사이드바가 접혀있는지 여부.</summary>
public bool IsCollapsed => _isCollapsed;
/// <summary>현재 활성 탭.</summary>
public string ActiveTab => _activeTab;
/// <summary>검색어 변경 이벤트.</summary>
public event TextChangedEventHandler? SearchTextChanged;
public AgentSidebarView()
{
@@ -44,189 +46,77 @@ public partial class AgentSidebarView : UserControl
UpdateTabHighlight("Chat");
}
/// <summary>탭을 외부에서 변경합니다.</summary>
// ── 외부 제어 ───────────────────────────────────────────────────────────
/// <summary>탭을 외부에서 변경합니다 (이벤트 발행 없음).</summary>
public void SetActiveTab(string tab)
{
_activeTab = tab;
UpdateTabHighlight(tab);
}
/// <summary>대화 이력 목록을 갱신합니다.</summary>
public void RefreshConversations(IEnumerable<ConversationItem> items)
{
ConversationList.Children.Clear();
// ── XAML 이벤트 핸들러 ──────────────────────────────────────────────────
string? lastGroup = null;
foreach (var item in items)
{
var group = GetDateGroup(item.UpdatedAt);
if (group != lastGroup)
{
lastGroup = group;
var header = new TextBlock
{
Text = group,
FontSize = 11,
FontWeight = FontWeights.SemiBold,
Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
Margin = new Thickness(8, 12, 0, 4),
};
ConversationList.Children.Add(header);
}
var border = new Border
{
CornerRadius = new CornerRadius(6),
Padding = new Thickness(10, 8, 10, 8),
Margin = new Thickness(0, 1, 0, 1),
Cursor = System.Windows.Input.Cursors.Hand,
Tag = item.Id,
Background = item.IsActive
? (TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent)
: Brushes.Transparent,
};
var title = new TextBlock
{
Text = item.Title,
FontSize = 12,
Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.White,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxWidth = 200,
};
border.Child = title;
border.MouseLeftButtonUp += (s, e) =>
{
if (s is Border b && b.Tag is string id)
ConversationSelected?.Invoke(this, id);
};
// 호버 효과
border.MouseEnter += (s, _) =>
{
if (s is Border b)
b.Background = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent;
};
border.MouseLeave += (s, _) =>
{
if (s is Border b && b.Tag is string id)
b.Background = items.Any(i => i.Id == id && i.IsActive)
? (TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent)
: Brushes.Transparent;
};
ConversationList.Children.Add(border);
}
}
#region
private void TabChat_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_activeTab = "Chat";
UpdateTabHighlight("Chat");
TabChanged?.Invoke(this, "Chat");
}
private void TabCowork_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_activeTab = "Cowork";
UpdateTabHighlight("Cowork");
TabChanged?.Invoke(this, "Cowork");
}
private void TabCode_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_activeTab = "Code";
UpdateTabHighlight("Code");
TabChanged?.Invoke(this, "Code");
}
private void BtnNewChat_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
private void BtnNewChat_Click(object sender, RoutedEventArgs e)
=> NewChatRequested?.Invoke(this, EventArgs.Empty);
private void BtnDeleteAll_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
private void SidebarTabChat_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (_activeTab == "Chat") return;
_activeTab = "Chat";
UpdateTabHighlight("Chat");
SidebarTabChanged?.Invoke(this, "Chat");
}
private void SidebarTabCowork_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (_activeTab == "Cowork") return;
_activeTab = "Cowork";
UpdateTabHighlight("Cowork");
SidebarTabChanged?.Invoke(this, "Cowork");
}
private void SidebarTabCode_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (_activeTab == "Code") return;
_activeTab = "Code";
UpdateTabHighlight("Code");
SidebarTabChanged?.Invoke(this, "Code");
}
private void SearchBox_TextChanged(object sender, TextChangedEventArgs e)
=> SearchTextChanged?.Invoke(sender, e);
private void BtnCategoryDrop_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
=> CategoryDropClicked?.Invoke(this, EventArgs.Empty);
private void BtnDeleteAll_Click(object sender, RoutedEventArgs e)
=> DeleteAllRequested?.Invoke(this, EventArgs.Empty);
private void BtnSettings_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
=> SettingsRequested?.Invoke(this, EventArgs.Empty);
private void BtnCollapse_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_isCollapsed = !_isCollapsed;
AnimateCollapse(_isCollapsed);
CollapseToggled?.Invoke(this, _isCollapsed);
}
private void TxtSearch_TextChanged(object sender, TextChangedEventArgs e)
{
// 검색 필터링은 ChatWindow에서 처리
}
#endregion
#region UI
// ── UI 헬퍼 ─────────────────────────────────────────────────────────────
private void UpdateTabHighlight(string tab)
{
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
var accentBrush = TryFindResource("AccentColor") as Brush
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
var transparentBrush = Brushes.Transparent;
TabChat.Background = tab == "Chat" ? accentBrush : transparentBrush;
TabCowork.Background = tab == "Cowork" ? accentBrush : transparentBrush;
TabCode.Background = tab == "Code" ? accentBrush : transparentBrush;
SidebarTabChat.Background = tab == "Chat" ? accentBrush : transparentBrush;
SidebarTabCowork.Background = tab == "Cowork" ? accentBrush : transparentBrush;
SidebarTabCode.Background = tab == "Code" ? accentBrush : transparentBrush;
// 선택 탭 텍스트 색상 조정
SetTabTextColor(TabChat, tab == "Chat");
SetTabTextColor(TabCowork, tab == "Cowork");
SetTabTextColor(TabCode, tab == "Code");
SetTabTextColor(SidebarTabChat, tab == "Chat");
SetTabTextColor(SidebarTabCowork, tab == "Cowork");
SetTabTextColor(SidebarTabCode, tab == "Code");
}
private void SetTabTextColor(Border tab, bool isActive)
private void SetTabTextColor(Border tabBorder, bool isActive)
{
if (tab.Child is TextBlock txt)
if (tabBorder.Child is TextBlock txt)
{
txt.Foreground = isActive
? Brushes.White
: (TryFindResource("PrimaryText") as Brush ?? Brushes.White);
}
}
private void AnimateCollapse(bool collapse)
{
var targetWidth = collapse ? 48.0 : 260.0;
var anim = new DoubleAnimation(Width, targetWidth, TimeSpan.FromMilliseconds(180))
{
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut }
};
BeginAnimation(WidthProperty, anim);
// 접힌 상태: 제목/검색/대화 이력 숨김
var vis = collapse ? Visibility.Collapsed : Visibility.Visible;
TxtTitle.Visibility = vis;
TxtSearch.Visibility = vis;
ConversationList.Visibility = vis;
}
private static string GetDateGroup(DateTime date)
{
var today = DateTime.Today;
if (date.Date == today) return "오늘";
if (date.Date == today.AddDays(-1)) return "어제";
if (date.Date > today.AddDays(-7)) return "이전 7일";
if (date.Date > today.AddDays(-30)) return "이전 30일";
return date.ToString("yyyy-MM");
}
#endregion
/// <summary>대화 이력 표시용 DTO.</summary>
public class ConversationItem
{
public string Id { get; set; } = "";
public string Title { get; set; } = "";
public DateTime UpdatedAt { get; set; }
public bool IsActive { get; set; }
}
}