AX Agent 창 드래그 성능 개선 및 레이아웃 재계산 지연 처리
Some checks failed
Release Gate / gate (push) Has been cancelled

- ChatWindow에 WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE 감지를 추가해 창 이동·리사이즈 루프를 별도로 관리함
- 이동 중에는 루트 visual을 BitmapCache로 전환하고 무거운 transcript/layout 재계산을 지연시켜 드래그 버벅임을 줄임
- SizeChanged에서 주제 스크롤/반응형 레이아웃/메시지 재렌더를 즉시 수행하지 않고 종료 시 한 번만 반영하도록 정리함
- README와 DEVELOPMENT 문서에 변경 목적과 검증 결과를 로컬 시각 기준으로 기록함
- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0, 오류 0)
This commit is contained in:
2026-04-06 15:28:29 +09:00
parent 421a2c97f9
commit d45698d397
3 changed files with 63 additions and 0 deletions

View File

@@ -1248,3 +1248,7 @@ MIT License
- IBM 연동형 vLLM 인증 경로를 점검한 결과, 기존 AX Agent는 등록 모델 인증 방식으로 `Bearer``CP4D`만 지원하고 `IBM IAM` 토큰 교환은 지원하지 않았다. 이 때문에 IBM Cloud 계열 watsonx/vLLM 게이트웨이에 API 키를 직접 Bearer로 보내면 `인증 실패 - API 키가 유효하지 않습니다.` 오류가 발생할 수 있었다.
- [IbmIamTokenService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/IbmIamTokenService.cs)를 추가하고 [LlmService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/LlmService.cs)에 `ibm_iam` 인증 타입을 연결해, 등록 모델의 API 키를 IBM IAM access token으로 교환한 뒤 Bearer 헤더에 넣도록 보강했다.
- [ModelRegistrationDialog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ModelRegistrationDialog.cs), [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs), [AppSettings.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/AppSettings.cs)도 함께 갱신해 등록 모델 인증 방식에 `IBM IAM (토큰 교환)`이 보이고 저장/표시되도록 맞췄다.
- 업데이트: 2026-04-06 15:26 (KST)
- AX Agent 창을 PC에서 드래그로 이동할 때 버벅이던 문제를 줄이기 위해 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `WM_ENTERSIZEMOVE` / `WM_EXITSIZEMOVE` 기반 이동·리사이즈 감지를 추가했다.
- 이동/리사이즈 루프 중에는 창 루트를 `BitmapCache`로 묶고, `SizeChanged`에 연결돼 있던 `UpdateTopicPresetScrollMode()`, `UpdateResponsiveChatLayout()`, `RenderMessages()` 재계산은 잠시 지연시킨 뒤 루프 종료 시 한 번만 반영하도록 바꿨다.
- 이 변경으로 창을 끌 때마다 무거운 transcript/레이아웃이 반복 갱신되던 경로를 줄여, AX Agent 창 이동 체감 속도를 개선했다.

View File

@@ -4960,3 +4960,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- Document update: 2026-04-06 13:36 (KST) - Made file-backed transcript actions state-aware in `ChatWindow.AgentEventRendering.cs`. The inline preview button now changes label by context, such as `변경 확인`, `작성 내용 보기`, `부분 결과 보기`, `오류 파일 보기`, or `승인 전 미리보기`, instead of showing the same generic action for every case.
- Document update: 2026-04-06 14:06 (KST) - Diagnosed IBM-connected vLLM authentication failures and confirmed AX only supported `bearer` and `cp4d` registered-model auth modes. Added `IbmIamTokenService.cs` and wired a new `ibm_iam` auth mode into `LlmService.cs` so IBM Cloud API keys are exchanged for IAM access tokens before being sent as Bearer credentials.
- Document update: 2026-04-06 14:06 (KST) - Updated the registered-model schema and UI surfaces to expose the new auth mode. `AppSettings.cs`, `SettingsViewModel.cs`, `ModelRegistrationDialog.cs`, and the AX Agent overlay model list in `ChatWindow.xaml.cs` now save/display `IBM IAM` alongside existing `Bearer` and `CP4D` modes.
- Document update: 2026-04-06 15:26 (KST) - Improved AX Agent window drag performance by handling `WM_ENTERSIZEMOVE` / `WM_EXITSIZEMOVE` in `ChatWindow.xaml.cs`. The window now enters a lightweight move-size mode while being dragged or resized.
- Document update: 2026-04-06 15:26 (KST) - During move-size loops the root visual is temporarily cached with `BitmapCache`, and the expensive `UpdateTopicPresetScrollMode()`, `UpdateResponsiveChatLayout()`, and `RenderMessages()` refresh path is deferred until the move/resize operation ends. This reduces the “heavy” feeling when moving the AX Agent window on desktop PCs.

View File

@@ -35,6 +35,9 @@ public partial class ChatWindow : Window
private bool _isStreaming;
private bool _sidebarVisible = true;
private double _sidebarExpandedWidth = 262;
private bool _isInWindowMoveSizeLoop;
private bool _pendingResponsiveLayoutRefresh;
private CacheMode? _cachedRootCacheModeBeforeMove;
private string _selectedCategory = ""; // "" = 전체
private readonly Dictionary<string, string> _tabSelectedCategory = new(StringComparer.OrdinalIgnoreCase)
{
@@ -274,6 +277,9 @@ public partial class ChatWindow : Window
KeyDown += ChatWindow_KeyDown;
MouseMove += (_, _) =>
{
if (_isInWindowMoveSizeLoop)
return;
if (TokenUsagePopup?.IsOpen == true)
CloseTokenUsagePopupIfIdle();
};
@@ -389,6 +395,12 @@ public partial class ChatWindow : Window
};
SizeChanged += (_, _) =>
{
if (_isInWindowMoveSizeLoop)
{
_pendingResponsiveLayoutRefresh = true;
return;
}
UpdateTopicPresetScrollMode();
if (UpdateResponsiveChatLayout())
RenderMessages(preserveViewport: true);
@@ -1067,6 +1079,15 @@ public partial class ChatWindow : Window
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == 0x0231) // WM_ENTERSIZEMOVE
{
BeginWindowMoveSizeLoop();
}
else if (msg == 0x0232) // WM_EXITSIZEMOVE
{
EndWindowMoveSizeLoop();
}
// WM_GETMINMAXINFO — 최대화 시 작업 표시줄 영역 확보
if (msg == 0x0024)
{
@@ -1087,6 +1108,42 @@ public partial class ChatWindow : Window
return IntPtr.Zero;
}
private void BeginWindowMoveSizeLoop()
{
if (_isInWindowMoveSizeLoop)
return;
_isInWindowMoveSizeLoop = true;
_pendingResponsiveLayoutRefresh = false;
if (Content is UIElement rootElement)
{
_cachedRootCacheModeBeforeMove = rootElement.CacheMode;
rootElement.CacheMode = new BitmapCache();
}
}
private void EndWindowMoveSizeLoop()
{
if (!_isInWindowMoveSizeLoop)
return;
_isInWindowMoveSizeLoop = false;
if (Content is UIElement rootElement)
rootElement.CacheMode = _cachedRootCacheModeBeforeMove;
_cachedRootCacheModeBeforeMove = null;
if (_pendingResponsiveLayoutRefresh)
{
_pendingResponsiveLayoutRefresh = false;
UpdateTopicPresetScrollMode();
if (UpdateResponsiveChatLayout())
RenderMessages(preserveViewport: true);
}
}
private void BtnMinimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
private void BtnMaximize_Click(object sender, RoutedEventArgs e)
{