[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:
@@ -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 구현 완료)
|
||||
|
||||
|
||||
37
src/AxCopilot/Views/ChatWindow.InputToolbar.cs
Normal file
37
src/AxCopilot/Views/ChatWindow.InputToolbar.cs
Normal 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());
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
38
src/AxCopilot/Views/ChatWindow.SessionHeaderBar.cs
Normal file
38
src/AxCopilot/Views/ChatWindow.SessionHeaderBar.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
80
src/AxCopilot/Views/ChatWindow.SidebarCompat.cs
Normal file
80
src/AxCopilot/Views/ChatWindow.SidebarCompat.cs
Normal 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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="" 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="" 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="" 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=""
|
||||
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="" 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="" 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="" 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="" 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="" 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"/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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="" FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
<TextBlock Text="" 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="" 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="" FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
<TextBlock x:Name="PlanIcon" Text="" 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="" FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
<TextBlock x:Name="PermIcon" Text="" 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="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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="" 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="" 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="" 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="" 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="" 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="" 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="" 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=""
|
||||
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="" 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="" 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="" 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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user