diff --git a/docs/NEXT_ROADMAP.md b/docs/NEXT_ROADMAP.md index e322392..bea076a 100644 --- a/docs/NEXT_ROADMAP.md +++ b/docs/NEXT_ROADMAP.md @@ -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` 제거 → `` 추가. | +| `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` | `` 인라인 110줄 → `` 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 구현 완료) diff --git a/src/AxCopilot/Views/ChatWindow.InputToolbar.cs b/src/AxCopilot/Views/ChatWindow.InputToolbar.cs new file mode 100644 index 0000000..043e875 --- /dev/null +++ b/src/AxCopilot/Views/ChatWindow.InputToolbar.cs @@ -0,0 +1,37 @@ +using System.Windows; + +namespace AxCopilot.Views; + +/// +/// Phase 17-UI-E: AgentInputArea InputToolbar 이벤트 연결. +/// @ 멘션, / 스킬, 📎 첨부 버튼을 기존 ChatWindow 동작에 위임합니다. +/// +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()); + }; + } +} diff --git a/src/AxCopilot/Views/ChatWindow.ModelSelector.cs b/src/AxCopilot/Views/ChatWindow.ModelSelector.cs index 7f39065..3554722 100644 --- a/src/AxCopilot/Views/ChatWindow.ModelSelector.cs +++ b/src/AxCopilot/Views/ChatWindow.ModelSelector.cs @@ -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) diff --git a/src/AxCopilot/Views/ChatWindow.PermissionMenu.cs b/src/AxCopilot/Views/ChatWindow.PermissionMenu.cs index 1592716..e74aa4c 100644 --- a/src/AxCopilot/Views/ChatWindow.PermissionMenu.cs +++ b/src/AxCopilot/Views/ChatWindow.PermissionMenu.cs @@ -101,12 +101,12 @@ public partial class ChatWindow AutoPermissionWarning.Visibility = Visibility.Collapsed; } - /// Phase 17-UI-B: 헤더 바 권한 칩 클릭 — 권한 팝업을 칩 위치에 표시. + /// Phase 17-UI-C: SessionHeaderBar 권한 칩 클릭 — 권한 팝업을 SessionHeaderBar 위치에 표시. 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; }); diff --git a/src/AxCopilot/Views/ChatWindow.SessionHeaderBar.cs b/src/AxCopilot/Views/ChatWindow.SessionHeaderBar.cs new file mode 100644 index 0000000..4dfa090 --- /dev/null +++ b/src/AxCopilot/Views/ChatWindow.SessionHeaderBar.cs @@ -0,0 +1,38 @@ +using System.Windows; + +namespace AxCopilot.Views; + +/// +/// Phase 17-UI-C: AgentSessionHeaderBar 이벤트 연결 및 상태 동기화. +/// SessionHeaderBar(UserControl) ↔ ChatWindow 기존 로직 브리지. +/// +public partial class ChatWindow +{ + /// Loaded 핸들러에서 호출 — SessionHeaderBar 이벤트 연결. + 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(); + } +} diff --git a/src/AxCopilot/Views/ChatWindow.SidebarCompat.cs b/src/AxCopilot/Views/ChatWindow.SidebarCompat.cs new file mode 100644 index 0000000..f468a6b --- /dev/null +++ b/src/AxCopilot/Views/ChatWindow.SidebarCompat.cs @@ -0,0 +1,80 @@ +using System.Windows; +using System.Windows.Controls; +using AxCopilot.Views.Controls; + +namespace AxCopilot.Views; + +/// +/// Phase 17-UI-D: AgentSidebarView 프록시 어댑터. +/// 34개 기존 ChatWindow 파셜 파일이 사이드바 내부 요소를 그대로 참조할 수 있도록 +/// 계산 프로퍼티(computed property)로 위임합니다. +/// +public partial class ChatWindow +{ + // ── 사이드바 내부 요소 프록시 (기존 코드 무수정 호환) ───────────────── + + /// 대화 목록 StackPanel — RefreshConversationList 등에서 직접 사용. + private StackPanel ConversationPanel => Sidebar.SidebarConversationPanel; + + /// 검색 TextBox — SearchBox_TextChanged 및 검색 필터링에서 사용. + private TextBox SearchBox => Sidebar.SidebarSearchBox; + + /// 카테고리 아이콘 TextBlock — UpdateCategoryLabel()에서 사용. + private TextBlock CategoryIcon => Sidebar.SidebarCategoryIcon; + + /// 카테고리 레이블 TextBlock — UpdateCategoryLabel()에서 사용. + private TextBlock CategoryLabel => Sidebar.SidebarCategoryLabel; + + /// 카테고리 드롭다운 Border — 팝업 PlacementTarget으로 사용. + private Border BtnCategoryDrop => Sidebar.SidebarCategoryDropTarget; + + /// 사용자 이니셜 TextBlock — SetupUserInfo()에서 사용. + private TextBlock UserInitialSidebar => Sidebar.SidebarUserInitial; + + /// 사용자 이름 TextBlock — SetupUserInfo()에서 사용. + private TextBlock UserNameText => Sidebar.SidebarUserName; + + /// 사용자 PC 텍스트 TextBlock — SetupUserInfo()에서 사용. + 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); + }; + } +} diff --git a/src/AxCopilot/Views/ChatWindow.TabSwitching.cs b/src/AxCopilot/Views/ChatWindow.TabSwitching.cs index a063d67..8a9ee1b 100644 --- a/src/AxCopilot/Views/ChatWindow.TabSwitching.cs +++ b/src/AxCopilot/Views/ChatWindow.TabSwitching.cs @@ -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() diff --git a/src/AxCopilot/Views/ChatWindow.xaml b/src/AxCopilot/Views/ChatWindow.xaml index 19f9e8e..57edadf 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml +++ b/src/AxCopilot/Views/ChatWindow.xaml @@ -185,121 +185,9 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -340,52 +228,10 @@ KeyDown="ChatTitleEdit_KeyDown"/> - + - - - - - - - - - - - - + + - - + @@ -54,21 +52,20 @@ - - + - + - + @@ -76,98 +73,91 @@ - - - - - - - - - - - - - + + - - - - - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AxCopilot/Views/Controls/AgentSidebarView.xaml.cs b/src/AxCopilot/Views/Controls/AgentSidebarView.xaml.cs index d076b45..a8276cc 100644 --- a/src/AxCopilot/Views/Controls/AgentSidebarView.xaml.cs +++ b/src/AxCopilot/Views/Controls/AgentSidebarView.xaml.cs @@ -6,37 +6,39 @@ using System.Windows.Media.Animation; namespace AxCopilot.Views.Controls; /// -/// Phase 32-B: 좌측 사이드바 (Claude.ai 스타일). -/// 탭 세그먼트(Chat/Cowork/Code) + 새 대화 + 검색 + 대화 이력. +/// Phase 17-UI-D: 좌측 사이드바 UserControl. +/// 기존 SidebarPanel Grid를 대체. ChatWindow 프록시 패턴으로 기존 코드와 호환. +/// 탭 세그먼트(Chat/Cowork/Code) + 카테고리 필터 + 대화 목록 + 사용자 계정. /// 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가 구독) ───────────────────────────────────────── /// 탭 전환 이벤트. 인자: 탭 이름 ("Chat", "Cowork", "Code"). - public event EventHandler? TabChanged; + public event EventHandler? SidebarTabChanged; /// 새 대화 요청 이벤트. public event EventHandler? NewChatRequested; - /// 대화 선택 이벤트. 인자: 대화 ID. - public event EventHandler? ConversationSelected; - /// 전체 삭제 요청 이벤트. public event EventHandler? DeleteAllRequested; - /// 설정 패널 토글 요청 이벤트. - public event EventHandler? SettingsRequested; + /// 카테고리 드롭다운 클릭 이벤트. + public event EventHandler? CategoryDropClicked; - /// 접기/펼치기 토글 이벤트. - public event EventHandler? CollapseToggled; - - /// 사이드바가 접혀있는지 여부. - public bool IsCollapsed => _isCollapsed; - - /// 현재 활성 탭. - public string ActiveTab => _activeTab; + /// 검색어 변경 이벤트. + public event TextChangedEventHandler? SearchTextChanged; public AgentSidebarView() { @@ -44,189 +46,77 @@ public partial class AgentSidebarView : UserControl UpdateTabHighlight("Chat"); } - /// 탭을 외부에서 변경합니다. + // ── 외부 제어 ─────────────────────────────────────────────────────────── + + /// 탭을 외부에서 변경합니다 (이벤트 발행 없음). public void SetActiveTab(string tab) { _activeTab = tab; UpdateTabHighlight(tab); } - /// 대화 이력 목록을 갱신합니다. - public void RefreshConversations(IEnumerable 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 - - /// 대화 이력 표시용 DTO. - public class ConversationItem - { - public string Id { get; set; } = ""; - public string Title { get; set; } = ""; - public DateTime UpdatedAt { get; set; } - public bool IsActive { get; set; } - } }