AX Agent 대화 목록 필터 프레젠테이션 분리 및 사이드바 구조 정리
Some checks failed
Release Gate / gate (push) Has been cancelled

- ChatWindow에서 대화 목록 필터/정렬 interaction과 선호 저장 로직을 ConversationFilterPresentation partial로 분리
- 실행 중 보기, 최근/활동 정렬, 관련 버튼 UI 갱신을 메인 창 orchestration 코드 밖으로 이동
- README와 DEVELOPMENT 문서에 2026-04-06 11:11 (KST) 기준 구조 개선 누적 완료 범위 반영
- dotnet build 검증 경고 0, 오류 0 확인
This commit is contained in:
2026-04-06 11:37:44 +09:00
parent 3ac8a7155f
commit 9aa99cdfe6
4 changed files with 121 additions and 107 deletions

View File

@@ -1194,3 +1194,6 @@ MIT License
- 업데이트: 2026-04-06 11:03 (KST)
- 좌측 sidebar의 검색/새 대화 interaction을 [ChatWindow.SidebarInteractionPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.SidebarInteractionPresentation.cs) 로 분리했다. 검색 트리거 hover, 새 대화 hover, 검색 열기/닫기 애니메이션이 메인 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 밖으로 이동해, 메인 창은 runtime/transcript orchestration에 더 집중하고 sidebar UX는 별도 presentation surface에서 다루게 정리했다.
- 큰 구조 개선 계획 기준으로는 이제 sidebar interaction까지 분리 완료 상태이며, 이후 남는 작업은 공통 시각 언어 polish나 실제 사용 흐름 기반 미세 UX 튜닝 같은 후속 개선 영역이다.
- 업데이트: 2026-04-06 11:11 (KST)
- 좌측 대화 목록의 필터/정렬 interaction을 [ChatWindow.ConversationFilterPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ConversationFilterPresentation.cs) 로 분리했다. 실행 중 보기, 최근/활동 정렬, 대화 목록 선호 저장/복원, 관련 버튼 UI 상태 갱신이 메인 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 밖으로 이동해 sidebar 상태 표현 책임이 더 응집도 있게 정리됐다.
- 이 단계까지 누적 완료된 구조 개선은 상태선/권한/도구 결과 카탈로그화, inline ask/plan 분리, footer/Git/preset/list/message/timeline/conversation management/sidebar interaction/filter 분리까지다. 이제 남는 건 큰 분리가 아니라 실제 시나리오 기반 polish와 공통 시각 언어 고도화다.

View File

@@ -4933,3 +4933,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- Document update: 2026-04-06 10:56 (KST) - With this pass, the remaining large structure-improvement track is effectively complete; follow-up work is now mostly UX polish and surface-level tuning rather than further decomposition of the main chat window orchestration file.
- Document update: 2026-04-06 11:03 (KST) - Split sidebar search/new-chat interactions out of `ChatWindow.xaml.cs` into `ChatWindow.SidebarInteractionPresentation.cs`. Hover state changes, sidebar search open/close transitions, and new-chat trigger behavior now live in a dedicated presentation partial.
- Document update: 2026-04-06 11:03 (KST) - This leaves the main chat window even more orchestration-focused and effectively closes the last obvious sidebar interaction block from the large structure-improvement plan. Remaining work is now follow-up UX polish rather than major decomposition.
- Document update: 2026-04-06 11:11 (KST) - Split conversation list filter/sort interactions and preference persistence out of `ChatWindow.xaml.cs` into `ChatWindow.ConversationFilterPresentation.cs`. Running-only toggles, recent/activity sort toggles, UI-state refresh, and conversation-list preference apply/persist logic now live in a dedicated presentation partial.
- Document update: 2026-04-06 11:11 (KST) - This keeps sidebar state presentation more cohesive and further narrows the main chat window file toward orchestration-only responsibilities. Remaining work is now post-plan polish rather than another major structural split.

View File

@@ -0,0 +1,116 @@
using System;
using System.Windows;
using System.Windows.Media;
using AxCopilot.Models;
namespace AxCopilot.Views;
public partial class ChatWindow
{
private void BtnFailedOnlyFilter_Click(object sender, RoutedEventArgs e) { }
private void BtnRunningOnlyFilter_Click(object sender, RoutedEventArgs e)
{
_runningOnlyFilter = false;
UpdateConversationRunningFilterUi();
PersistConversationListPreferences();
RefreshConversationList();
}
private void BtnQuickRunningFilter_Click(object sender, RoutedEventArgs e)
=> BtnRunningOnlyFilter_Click(sender, e);
private void BtnQuickHotSort_Click(object sender, RoutedEventArgs e)
{
_sortConversationsByRecent = false;
UpdateConversationSortUi();
PersistConversationListPreferences();
RefreshConversationList();
}
private void BtnConversationSort_Click(object sender, RoutedEventArgs e)
{
_sortConversationsByRecent = !_sortConversationsByRecent;
UpdateConversationSortUi();
PersistConversationListPreferences();
RefreshConversationList();
}
private void UpdateConversationFailureFilterUi()
{
_failedOnlyFilter = false;
UpdateSidebarModeMenu();
}
private void UpdateConversationRunningFilterUi()
{
if (BtnRunningOnlyFilter == null || RunningOnlyFilterLabel == null)
return;
BtnRunningOnlyFilter.Background = _runningOnlyFilter
? BrushFromHex("#DBEAFE")
: Brushes.Transparent;
BtnRunningOnlyFilter.BorderBrush = _runningOnlyFilter
? BrushFromHex("#93C5FD")
: Brushes.Transparent;
BtnRunningOnlyFilter.BorderThickness = _runningOnlyFilter
? new Thickness(1)
: new Thickness(0);
RunningOnlyFilterLabel.Foreground = _runningOnlyFilter
? BrushFromHex("#1D4ED8")
: (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
RunningOnlyFilterLabel.Text = _runningConversationCount > 0
? $"진행 {_runningConversationCount}"
: "진행";
BtnRunningOnlyFilter.ToolTip = _runningOnlyFilter
? "실행 중인 대화만 표시 중"
: _runningConversationCount > 0
? $"현재 실행 중인 대화 {_runningConversationCount}개 보기"
: "현재 실행 중인 대화만 보기";
UpdateSidebarModeMenu();
}
private void UpdateConversationSortUi()
{
if (BtnConversationSort == null || ConversationSortLabel == null)
return;
ConversationSortLabel.Text = _sortConversationsByRecent ? "최근" : "활동";
BtnConversationSort.Background = _sortConversationsByRecent
? BrushFromHex("#EFF6FF")
: BrushFromHex("#F8FAFC");
BtnConversationSort.BorderBrush = _sortConversationsByRecent
? BrushFromHex("#93C5FD")
: BrushFromHex("#E2E8F0");
BtnConversationSort.BorderThickness = new Thickness(1);
ConversationSortLabel.Foreground = _sortConversationsByRecent
? BrushFromHex("#1D4ED8")
: (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
BtnConversationSort.ToolTip = _sortConversationsByRecent
? "최신 업데이트 순으로 보는 중"
: "에이전트 활동량과 실패를 우선으로 보는 중";
}
private void ApplyConversationListPreferences(ChatConversation? conv)
{
_failedOnlyFilter = false;
_runningOnlyFilter = false;
_sortConversationsByRecent = string.Equals(conv?.ConversationSortMode, "recent", StringComparison.OrdinalIgnoreCase);
UpdateConversationFailureFilterUi();
UpdateConversationRunningFilterUi();
UpdateConversationSortUi();
}
private void PersistConversationListPreferences()
{
lock (_convLock)
{
var session = _appState.ChatSession;
if (session == null)
return;
session.SaveConversationListPreferences(_activeTab, _failedOnlyFilter, _runningOnlyFilter, _sortConversationsByRecent, _storage);
_currentConversation = session.CurrentConversation;
}
}
}

View File

@@ -2093,90 +2093,6 @@ public partial class ChatWindow : Window
_conversationSearchTimer.Start();
}
private void BtnFailedOnlyFilter_Click(object sender, RoutedEventArgs e) { }
private void BtnRunningOnlyFilter_Click(object sender, RoutedEventArgs e)
{
_runningOnlyFilter = false;
UpdateConversationRunningFilterUi();
PersistConversationListPreferences();
RefreshConversationList();
}
private void BtnQuickRunningFilter_Click(object sender, RoutedEventArgs e)
=> BtnRunningOnlyFilter_Click(sender, e);
private void BtnQuickHotSort_Click(object sender, RoutedEventArgs e)
{
_sortConversationsByRecent = false;
UpdateConversationSortUi();
PersistConversationListPreferences();
RefreshConversationList();
}
private void BtnConversationSort_Click(object sender, RoutedEventArgs e)
{
_sortConversationsByRecent = !_sortConversationsByRecent;
UpdateConversationSortUi();
PersistConversationListPreferences();
RefreshConversationList();
}
private void UpdateConversationFailureFilterUi()
{
_failedOnlyFilter = false;
UpdateSidebarModeMenu();
}
private void UpdateConversationRunningFilterUi()
{
if (BtnRunningOnlyFilter == null || RunningOnlyFilterLabel == null)
return;
BtnRunningOnlyFilter.Background = _runningOnlyFilter
? BrushFromHex("#DBEAFE")
: Brushes.Transparent;
BtnRunningOnlyFilter.BorderBrush = _runningOnlyFilter
? BrushFromHex("#93C5FD")
: Brushes.Transparent;
BtnRunningOnlyFilter.BorderThickness = _runningOnlyFilter
? new Thickness(1)
: new Thickness(0);
RunningOnlyFilterLabel.Foreground = _runningOnlyFilter
? BrushFromHex("#1D4ED8")
: (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
RunningOnlyFilterLabel.Text = _runningConversationCount > 0
? $"진행 {_runningConversationCount}"
: "진행";
BtnRunningOnlyFilter.ToolTip = _runningOnlyFilter
? "실행 중인 대화만 표시 중"
: _runningConversationCount > 0
? $"현재 실행 중인 대화 {_runningConversationCount}개 보기"
: "현재 실행 중인 대화만 보기";
UpdateSidebarModeMenu();
}
private void UpdateConversationSortUi()
{
if (BtnConversationSort == null || ConversationSortLabel == null)
return;
ConversationSortLabel.Text = _sortConversationsByRecent ? "최근" : "활동";
BtnConversationSort.Background = _sortConversationsByRecent
? BrushFromHex("#EFF6FF")
: BrushFromHex("#F8FAFC");
BtnConversationSort.BorderBrush = _sortConversationsByRecent
? BrushFromHex("#93C5FD")
: BrushFromHex("#E2E8F0");
BtnConversationSort.BorderThickness = new Thickness(1);
ConversationSortLabel.Foreground = _sortConversationsByRecent
? BrushFromHex("#1D4ED8")
: (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
BtnConversationSort.ToolTip = _sortConversationsByRecent
? "최신 업데이트 순으로 보는 중"
: "에이전트 활동량과 실패를 우선으로 보는 중";
}
private static string FormatDate(DateTime dt)
{
var diff = DateTime.Now - dt;
@@ -6259,29 +6175,6 @@ public partial class ChatWindow : Window
UpdateTaskSummaryIndicators();
}
private void ApplyConversationListPreferences(ChatConversation? conv)
{
_failedOnlyFilter = false;
_runningOnlyFilter = false;
_sortConversationsByRecent = string.Equals(conv?.ConversationSortMode, "recent", StringComparison.OrdinalIgnoreCase);
UpdateConversationFailureFilterUi();
UpdateConversationRunningFilterUi();
UpdateConversationSortUi();
}
private void PersistConversationListPreferences()
{
lock (_convLock)
{
var session = _appState.ChatSession;
if (session == null)
return;
session.SaveConversationListPreferences(_activeTab, _failedOnlyFilter, _runningOnlyFilter, _sortConversationsByRecent, _storage);
_currentConversation = session.CurrentConversation;
}
}
private static string GetRunStatusLabel(string? status)
=> status switch
{