From f7cafe0cfc632ff5e9d1aef345abfe8e37a5bb79 Mon Sep 17 00:00:00 2001 From: lacvet Date: Sun, 5 Apr 2026 11:51:43 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9F=B0=EC=B2=98=20Agent=20Compare=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=201=EC=B0=A8=20=EC=9D=B4=EC=8B=9D=20?= =?UTF-8?q?=EB=B0=8F=20=ED=98=84=EC=9E=AC=20=EB=9F=B0=EC=B2=98=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agent Compare 기준으로 런처 빠른 실행 칩, 검색 히스토리 탐색, 선택 항목 미리보기 패널을 현재 런처에 이식 - 하단 위젯 바, QuickLook(F3), 화면 OCR(F4), 관련 서비스/partial 파일을 현재 LauncherWindow/LauncherViewModel 구조에 연결 - UsageRankingService 상위 항목 조회와 SearchHistoryService를 추가해 실행 상위 경로/검색 기록이 실제 런처 동작에 반영되도록 정리 - README.md, docs/DEVELOPMENT.md에 이식 범위와 검증 결과를 2026-04-05 11:58 (KST) 기준으로 기록 검증 결과 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 경고 0 / 오류 0 --- README.md | 290 ++++++++++++++ docs/DEVELOPMENT.md | 360 ++++++++++++++++++ src/AxCopilot/Models/QuickActionChip.cs | 14 + .../Services/PerformanceMonitorService.cs | 133 +++++++ .../Services/SearchHistoryService.cs | 108 ++++++ src/AxCopilot/Services/ServerStatusService.cs | 115 ++++++ src/AxCopilot/Services/UsageRankingService.cs | 17 + .../Services/WeatherWidgetService.cs | 52 +++ .../LauncherViewModel.LauncherExtras.cs | 322 ++++++++++++++++ .../ViewModels/LauncherViewModel.Widgets.cs | 114 ++++++ src/AxCopilot/ViewModels/LauncherViewModel.cs | 19 +- src/AxCopilot/Views/LauncherWindow.Shell.cs | 67 ++++ src/AxCopilot/Views/LauncherWindow.Widgets.cs | 209 ++++++++++ src/AxCopilot/Views/LauncherWindow.xaml | 257 ++++++++++++- src/AxCopilot/Views/LauncherWindow.xaml.cs | 161 +++++++- src/AxCopilot/Views/QuickLookWindow.xaml | 161 ++++++++ src/AxCopilot/Views/QuickLookWindow.xaml.cs | 143 +++++++ 17 files changed, 2518 insertions(+), 24 deletions(-) create mode 100644 src/AxCopilot/Models/QuickActionChip.cs create mode 100644 src/AxCopilot/Services/PerformanceMonitorService.cs create mode 100644 src/AxCopilot/Services/SearchHistoryService.cs create mode 100644 src/AxCopilot/Services/ServerStatusService.cs create mode 100644 src/AxCopilot/Services/WeatherWidgetService.cs create mode 100644 src/AxCopilot/ViewModels/LauncherViewModel.LauncherExtras.cs create mode 100644 src/AxCopilot/ViewModels/LauncherViewModel.Widgets.cs create mode 100644 src/AxCopilot/Views/LauncherWindow.Shell.cs create mode 100644 src/AxCopilot/Views/LauncherWindow.Widgets.cs create mode 100644 src/AxCopilot/Views/QuickLookWindow.xaml create mode 100644 src/AxCopilot/Views/QuickLookWindow.xaml.cs diff --git a/README.md b/README.md index 22ac72e..9e46e75 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,51 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-05 11:22 (KST) +- AX Agent 채팅 복구 1차로 컴포저를 하단 고정 배치로 조정해 세로 공간을 꽉 채우며 커 보이던 문제를 줄였습니다. +- 전송 직후 사용자 버블을 직접 UI에 꽂지 않고 대화 모델 기반 `RenderMessages()` 재렌더를 먼저 타도록 정리해, 중복 렌더와 빈 버블 누적 가능성을 낮췄습니다. +- Cowork/Code의 실행 이벤트 배너는 `실행 로그 표시`를 켠 경우에만 즉시 채팅 본문에 보이도록 바꿔 작업 중 플래시처럼 남던 잔상 UI를 줄였습니다. +- Cowork/Code는 실행 시작 시 해당 대화의 `실행 로그 표시`를 먼저 끄도록 바꿔, 중간 재렌더가 들어와도 이벤트 배너가 다시 섞이지 않도록 조정했습니다. +- Chat 탭은 비스트리밍 응답 경로에 맞춰 임시 스트리밍 카드 자체를 만들지 않도록 바꿨습니다. 이제 Chat은 최종 assistant 메시지만 모델에 반영되고 재렌더됩니다. +- Cowork/Code도 최종 응답형 경로에 맞춰 임시 스트리밍 카드를 만들지 않도록 정리했습니다. 이제 임시 빈 assistant 카드 없이 최종 응답만 대화 모델에 반영됩니다. +- 메인 설정 탭 가시성 로직에서도 `AX Agent` 탭은 항상 숨기도록 고정해, 일반 설정과 AX Agent 내부 설정이 다시 갈라지지 않게 정리했습니다. +- AX Agent 채팅 전송 경로에서 빈 assistant 메시지를 먼저 대화 모델에 넣던 흐름을 제거했습니다. 이제 응답이 끝난 뒤 최종 assistant 메시지만 추가되어, 토큰은 갔는데 빈 말풍선만 남는 현상을 줄입니다. +- 메인 설정 `TabControl`에서 구형 AX Agent 탭이 선택되더라도 내부 설정 바로가기로 즉시 우회되도록 연결해, 숨김 탭 경로로 다시 들어가는 흐름을 막았습니다. +- 메인 설정에만 남아 있던 `Temperature`도 AX Agent 내부 설정 오버레이에 추가했습니다. 이제 내부 설정에서 값 확인과 수정이 가능하며, 포커스가 빠지면 0.0~2.0 범위로 정규화해 저장됩니다. +- `claw-code`의 입력 처리/실행 분리 흐름을 참고해 AX Agent 내부 실행 엔진 `AxAgentExecutionEngine`을 추가했습니다. ChatWindow는 전송 메시지 조립과 최종 assistant 메시지 반영을 이 엔진으로 넘기기 시작했습니다. + + +- 업데이트: 2026-04-05 07:11 (KST) +- AX Agent 하단 바를 다시 정리해 코워크/코드 탭에서는 중복으로 보이던 보조 칩(`워크스페이스`, `파일`, `간략`, `로컬/워크트리` 묶음)이 더 이상 나타나지 않도록 정리했습니다. 작업 폴더 정보는 기존 폴더 경로 영역만 남기고, 노란 표시로 보이던 중복 보조 영역은 제거했습니다. +- `데이터 미활용` 버튼은 외곽 테두리선 없이 보이도록 바꿔 하단 옵션 줄이 덜 부풀어 보이게 정리했습니다. +- 업데이트: 2026-04-05 07:08 (KST) +- AX Agent 체감 속도 개선을 위해 대화 메타 캐시를 보강했습니다. 대화 목록 메타를 다시 읽을 때마다 매번 전체 정렬을 반복하지 않도록 정렬 결과를 별도로 캐시해, 사이드바 대화 목록과 분류 계산이 잦은 흐름의 부담을 줄였습니다. +- AX Agent 내부 설정/하단 옵션 반영 시 같은 값인데도 현재 대화 설정을 반복 저장하던 경로를 줄였습니다. 권한, 데이터 활용, 무드, 출력 형식이 실제로 바뀐 경우에만 대화 저장이 일어나도록 바꿔 작은 옵션 변경 때의 지연을 덜었습니다. +- 대화 검색창은 입력할 때마다 즉시 전체 목록을 다시 그리지 않고 짧게 디바운스되도록 조정해, 검색어를 빠르게 입력할 때의 버벅임을 줄였습니다. +- 업데이트: 2026-04-05 02:00 (KST) +- AX Agent 내부 톱니 설정 오버레이의 왼쪽 분류 탭도 복구했던 기준에 맞춰 `기본 / 채팅 / 코워크 / 코드 / 개발자 / 도구 / 스킬/차단` 구조로 다시 정리했습니다. 단순히 항목만 추가한 것이 아니라, 실제 오버레이 네비게이션 자체를 같은 분류 기준으로 바꾸고 각 탭에서 해당 설정군만 보이도록 다시 묶었습니다. +- 업데이트: 2026-04-05 01:46 (KST) +- AX Agent 안의 톱니 아이콘이 실제로 여는 대상이 별도 창이 아니라 채팅 내부 오버레이임을 다시 확인하고, 누락된 설정을 그 실제 오버레이로 옮겼습니다. 이제 AX Agent 내부 설정 오버레이의 고급 섹션에서 `프로젝트 규칙 자동 반영`, `에이전트 메모리`, `최대 Agent Pass`, `Code용 Plan/Worktree/Team/Cron 도구` 항목을 직접 보고 저장할 수 있습니다. +- 업데이트: 2026-04-05 01:40 (KST) +- AX Agent 내부의 톱니 설정 창에 메인/오버레이 설정에 남아 있던 항목을 추가로 옮겼습니다. 이제 내부 설정에서 기본 출력 형식, 기본 디자인 무드, 프로젝트 규칙 자동 반영, 에이전트 메모리, 최대 Agent Pass, Code용 Plan/Worktree/Team/Cron 도구 토글까지 직접 보고 저장할 수 있습니다. +- 채팅 하단 입력부와 옵션 버튼의 과한 pill 형태도 다시 줄였습니다. 하단 옵션 버튼과 입력 박스의 코너 반경을 눌러 타원형 느낌보다 밀도 있는 업무형 형태에 가깝게 정리했습니다. +- 업데이트: 2026-04-05 01:35 (KST) +- AX Agent 코워크 대화 목록 필터를 프로젝트(작업 폴더) 기준이 아니라 작업 유형 기준으로 다시 정리했습니다. 이제 코워크의 상단 빈 상태, 분류 드롭다운, 대화 목록 필터가 모두 프리셋/카테고리 기반 작업 유형 흐름에 맞춰 동작합니다. +- 코드 탭은 기존처럼 프로젝트(워크스페이스) 기준 필터를 유지해, 코워크와 코드가 각자 맞는 분류 기준을 사용하도록 분리했습니다. +- 업데이트: 2026-04-05 01:22 (KST) +- AX Agent 내부 설정 창의 `도구`, `스킬/차단` 탭에 숨겨져 있던 설정을 추가로 옮겼습니다. 이제 내부 설정에서 도구 노출 목록, 도구 훅, 스킬 폴더, 슬래시 팝업 개수, 드래그 앤 드롭 AI 액션, 폴백 모델, MCP 서버를 직접 확인하고 저장할 수 있습니다. +- 채팅 하단 데이터 활용 옵션의 과한 타원형 pill 테두리는 둥근 사각형으로 정리해 하단 옵션 바가 덜 부풀어 보이도록 다듬었습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 + +- 업데이트: 2026-04-05 01:15 (KST) +- AX Agent 내부 설정 저장 경로를 다시 점검해, 저장 버튼 전에도 원본 설정 객체를 바로 바꾸던 흐름을 로컬 상태 기반으로 정리했습니다. 이제 서비스/테마/표현 수준 선택은 저장 버튼을 눌렀을 때만 실제 설정에 반영됩니다. +- 내부 설정 창에서 빠져 있던 `vLLM TLS 우회`, 활성 서비스별 모델 동기화, 전역 호환 모델 필드 저장도 다시 연결해 저장 후 재실행 시 값이 어긋나는 문제를 줄였습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 + +- 업데이트: 2026-04-05 01:12 (KST) +- AX Agent 내부 설정 창의 분류 기준을 이전 커밋 구조에 맞춰 다시 정리했습니다. 단순 섹션형이던 인앱 설정을 `기본 / 채팅 / 코워크 / 코드 / 개발자 / 도구 / 스킬·차단` 흐름으로 되돌릴 수 있는 기반을 복구했고, 우선 `기본/채팅/코워크/코드/개발자/도구` 탭 전환과 핵심 저장 경로를 다시 연결했습니다. +- `AX Agent 사용` 토글과 `표현 수준` 저장도 내부 설정 창에서 다시 관리되도록 연결했고, 설정 저장 시 `AiEnabled` 값이 강제로 다시 켜지던 정규화 경로도 함께 수정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 - 업데이트: 2026-04-05 00:58 (KST) - `Agent Compare/AX Copilot`의 개발 문서와 런처 소스를 대조해 AX Commander 신규 기능 묶음을 이식했습니다. 빠른 링크, 파일 태그, 알림 센터, 포모도로, 파일 브라우저, 핫키 관리, OCR, 세션/스케줄/매크로, Git/정규식/네트워크/압축/해시/SSH/UUID/JWT/QR 등 비교본에 있던 다수의 런처 핸들러를 현재 앱에 등록했습니다. @@ -424,6 +469,14 @@ ow + toggle 시각 언어로 통일했습니다. - DraftQueue 패널 상단에 실행 중 / 다음 / 보류 / 완료 / 실패 요약 pill을 추가하고, composer 상단의 모델/컨텍스트/프리셋 줄도 더 낮고 평평한 밀도로 정리했습니다. - 브랜치/워크트리 패널에는 공통 요약 strip을 추가해 현재 상태를 같은 시각 언어로 보여주도록 맞췄고, 저장소 루트 `.gitignore`에는 빌드 산출물·IDE 파일·OS 잡파일·비밀정보 패턴을 추가했습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 07:17 (KST) +- 별도 `AX Agent 설정` 창과 AX Agent 내부 설정 오버레이에서 `AX Agent 사용` 항목을 숨겨, 작업 중 자주 쓰지 않는 전역 AI 사용 토글이 설정 메뉴를 차지하지 않도록 정리했습니다. +- 별도 `AX Agent 설정` 창에서는 `표현 수준` 선택 카드도 함께 숨겨 기본 탭 상단을 더 단순하게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 07:24 (KST) +- `설정 > AX Agent`에만 남아 있던 스킬 목록을 별도 `AX Agent 설정` 창 안 `스킬/차단` 탭으로 복구해, 현재 로드된 슬래시 스킬을 내장/고급 그룹으로 다시 확인할 수 있게 했습니다. +- 별도 `AX Agent 설정` 창은 저장 시 스킬 폴더 기준으로 다시 로드해 목록이 바로 갱신되도록 연결했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 - 업데이트: 2026-04-04 23:23 (KST) - AX Agent는 이제 설정 서비스 변경 이벤트를 직접 구독해 메인 설정, AX Agent 설정, 저장 경로와 관계없이 테마/권한/데이터 활용/모델 라벨/composer/대기열 UI를 즉시 다시 읽어오도록 fan-out 경로를 통합했습니다. - AX Agent 설정 저장 경로에서 표현 수준을 `rich`로 고정 덮어쓰던 처리도 제거해, 사용자가 선택한 `풍부하게 / 적절하게 / 간단하게` 값이 다른 설정 저장 흐름에서도 유지되도록 보정했습니다. @@ -433,6 +486,243 @@ ow + toggle 시각 언어로 통일했습니다. - AX Agent 하단 컨텍스트 카드 툴팁에 최근 압축 이력을 추가해 마지막 자동/수동 compact 시각, 압축 전후 토큰, 실제 절감량을 다시 확인할 수 있게 했습니다. - 수동 `/compact` 실행과 전송 전 자동 컨텍스트 압축 모두 같은 compaction 통계 경로를 타도록 맞춰, compact 결과를 일회성 토스트가 아니라 이후 UI에서도 계속 확인할 수 있도록 보강했습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 07:39 (KST) +- `Agent Compare`의 트레이 메뉴 경로와 현재 구현을 대조해, 우클릭 메뉴가 열릴 때마다 `Show + UpdateLayout`로 창 크기를 다시 확정하던 흐름을 제거하고 메뉴 크기 측정값을 캐시하도록 바꿨습니다. +- 앱 유휴 시점에 트레이 메뉴를 미리 측정해 첫 우클릭에서 초기 레이아웃 비용이 몰리지 않도록 조정했고, AI 항목 가시성처럼 열기 직전 바뀌는 항목만 크기 캐시를 다시 계산하도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 07:45 (KST) +- AX Agent 내부 설정 오버레이의 `뒤로가기` 버튼을 오른쪽 본문 헤더에서 제거하고, 왼쪽 설정 제목 영역에 화살표와 함께 합쳐 배치해 설정 패널 접기/닫기와 혼동되지 않도록 정리했습니다. +- 본문 스크롤 영역은 헤더 빈 줄 없이 바로 시작하도록 올려, 설정 화면 진입 시 첫 섹션이 더 자연스럽게 이어지도록 조정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 07:49 (KST) +- AX Agent 상단 좌측의 사이드바 접기 버튼을 기존 큰 아이콘형 고스트 버튼에서, Claude 계열처럼 작은 라운드 사각 안에 3줄 메뉴가 들어간 얇은 토글 버튼으로 바꿨습니다. +- 열림/닫힘 상태에서 글리프를 바꾸던 예전 처리도 제거해, 상단 바가 덜 요란하고 더 안정적인 작업형 헤더로 보이도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 07:54 (KST) +- Chat 빈 화면의 상단 아이콘이 탭 메뉴 바와 가까워 보이던 배치를 내려, 빈 상태 헤더가 상단 메뉴와 겹쳐 보이지 않도록 여백을 조정했습니다. +- Chat 탭에서는 하단의 컨텍스트/압축 카드가 보이지 않도록 분기해, 토큰 압축 UI는 Cowork/Code에서만 유지되게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:00 (KST) +- 런처 하단에 남아 있던 색인 상태 문구를 점검한 결과, 인덱스 상태 표시와 토스트 오버레이가 같은 타이머를 공유해 자동 숨김이 꼬일 수 있는 구조를 확인했습니다. +- [LauncherWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.xaml.cs) 에서 토스트 타이머와 인덱스 상태 타이머를 분리하고, 인덱스 재구축 시작/완료 문구를 공통 `ShowIndexStatus(...)` 경로로 묶어 일정 시간 뒤 확실히 사라지도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:06 (KST) +- AX Agent 내부 설정 오버레이에서 `Fast`, `의사결정 수준`, `실행 전 계획`, `권한 모드`, `기본 출력 형식`, `테마 스타일`, `운영 모드`, `폴더 데이터 활용`처럼 글자가 순환하던 버튼을 커스텀 콤보박스로 교체했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml)에 오버레이 전용 `OverlayComboBox` 스타일과 각 항목용 콤보를 넣고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서는 선택 변경 시 기존 저장 흐름을 그대로 타도록 전용 핸들러를 연결했습니다. +- 운영 모드처럼 보호가 필요한 항목은 콤보박스로 바뀐 뒤에도 기존 비밀번호 확인을 유지했고, 나머지 항목은 선택 즉시 AX Agent 내부 설정에 반영되도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:15 (KST) +- 별도 `AX Agent 설정` 화면에서 `AX Agent 사용`, `표현 수준` 항목은 다시 보이지 않도록 정리했고, 내부 저장값은 `AI 사용 = 활성`, `표현 수준 = 풍부하게`로 고정되게 보정했습니다. +- [AgentSettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/AgentSettingsWindow.xaml), [AgentSettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/AgentSettingsWindow.xaml.cs) 에서 해당 행을 숨기고 저장 시 강제로 켜진 상태와 `rich` 값을 유지하도록 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:14 (KST) +- 배포용 [build.bat](/E:/AX%20Copilot%20-%20Codex/build.bat)을 루트 절대 경로 기준으로 다시 작성해, 작업 폴더에 따라 `dist`, `payload.zip`, 인스톨러 복사 경로가 꼬이던 문제를 막았습니다. +- 스크립트는 이제 `main app publish -> optional obfuscation check -> AxKeyEncryptor publish -> payload.zip 생성 -> installer build -> dist 정리` 순서로 고정 동작하고, 외부 난독화 도구가 없으면 `보호 미적용` 경고를 명확히 출력합니다. +- 현재 레포에는 실제 난독화 도구 설정이 없어서, 배포본 보호 수준은 `PDB/XML/debug metadata 제거`까지이며 진짜 디컴파일 방지는 아직 미구성 상태임을 확인했습니다. +- 검증: `cmd /c build.bat` 실행 기준 메인 앱 publish, 인스톨러 빌드, `dist\AxCopilot_Setup.exe` 복사까지 정상 완료 +- 업데이트: 2026-04-05 07:59 (KST) +- `설정 > AX Agent > 공통`에서 계속 남아 있던 `AX Agent 사용`, `표현 수준` 행을 실제 메인 설정창 기준으로 다시 숨겼고, 표현 수준 값은 런타임 정규화와 설정 초기화 양쪽에서 `풍부하게(rich)`로 고정되도록 보정했습니다. +- `설정 > 기능 > 응답 설정`을 포함한 `?` 도움말 툴팁은 라이트 테마에서도 글자가 사라지지 않도록 `HelpTooltipStyle` 배경을 고대비 다크 톤으로 바꾸고 텍스트 전경색을 흰색으로 강제했습니다. +- `Agent Compare`와 비교해 빠져 있던 런처 `마지막 위치 기억` 설정을 복구하고, AX Commander가 숨겨질 때 마지막 좌표를 저장한 뒤 다음 표시 때 같은 위치를 복원하도록 연결했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` +- 업데이트: 2026-04-05 08:02 (KST) +- AX Agent가 열리자마자 죽던 원인을 앱 로그로 확인했고, `ChatWindow`가 `HelpTooltipStyle`을 찾지 못해 `XamlParseException`이 발생하고 있었습니다. +- [App.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/App.xaml)에 `HelpTooltipStyle`을 전역 리소스로 올려, AX Agent/설정/내부 오버레이가 모두 같은 도움말 툴팁 스타일을 공통으로 찾도록 수정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:08 (KST) +- `설정 > AX Agent`에 있던 도구/스킬 설명창이 내부 AX Agent 설정 오버레이에는 빠져 있던 상태를 보완해, [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml)의 `도구`, `스킬/차단` 탭 상단에 설명 블록을 다시 넣었습니다. +- `도구` 탭에는 훅 동작 흐름과 활용 예시, `스킬/차단` 탭에는 스킬 파일 구조, 기본 폴더 경로, MCP/폴백 모델/드래그 드롭 관리 범위를 안내하는 설명을 복구했고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 탭 전환 시 해당 설명창만 보이도록 연결했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 코워크 좌측 패널 상단 필터 메뉴는 동작이 이미 `작업 유형` 기준이었지만, 메뉴 라벨만 예전 `워크스페이스`로 남아 있던 부분을 `작업 유형`으로 수정했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 `SidebarCoworkMenu` 라벨을 코워크 기준에 맞게 정리했고, Code 탭의 `워크스페이스` 라벨은 그대로 유지했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:18 (KST) +- AX Agent 입력창에서 텍스트를 치면 입력 영역이 비정상적으로 길어지고, 입력 중 화면이 자주 번쩍이던 문제를 함께 수정했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 입력 행 `Grid.RowDefinition`을 `Auto`로 바꾸고 `InputBox`를 상단 정렬로 고정해, Code 탭에서 입력창이 남는 공간을 끌어먹으며 비대해지던 레이아웃 문제를 막았습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에는 `_inputUiRefreshTimer`를 추가해, 타이핑 중 매 글자마다 실행되던 `RefreshContextUsageVisual()`과 `RefreshDraftQueueUi()`를 짧게 디바운스해서 입력 중 깜빡임과 과한 리렌더를 줄였습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:24 (KST) +- 모델 선택 팝업 하단에 중복으로 보이던 보조 UI도 정리했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 모델 리스트 아래 반복되던 `InlineModelChipPanel`은 숨기고, 맨 아래 `계획`, `권한` 빠른 버튼은 `Visibility="Collapsed"`로 내려 팝업 안 중복 제어를 제거했습니다. +- 안내 문구도 `서비스, 모델, 추론을 여기서 바로 바꿉니다`로 맞춰, 이 팝업이 실제로 제공하는 항목만 설명하도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:28 (KST) +- AX Agent 채팅 초기 프리셋 카드 영역의 세로 스크롤바도 항상 고정처럼 보이던 부분을 조정했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `TopicPresetScrollViewer` 이름을 부여하고 기본 상태를 `VerticalScrollBarVisibility="Disabled"`로 바꿔, 정상 크기에서는 스크롤바 여백이 먼저 보이지 않도록 했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `UpdateTopicPresetScrollMode()`를 추가해 프리셋 버튼 재구성 후와 창 크기 변경 시 `ExtentHeight`/`ViewportHeight`를 비교하고, 실제로 넘칠 때만 세로 스크롤을 `Auto`로 켜도록 했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:31 (KST) +- AX Agent 입력창 위 `후속 요청` 카드에는 타이핑 중인 현재 입력을 미리 보여주지 않도록 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `RefreshDraftQueueUi()`에서 `DraftPreviewCard`를 상시 접고, 후속 요청은 실시간 입력 미리보기가 아니라 엔터로 실제 대기열에 들어간 뒤 아래 대기열 목록에만 반영되도록 바꿨습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:34 (KST) +- AX Agent 빈 상태 프리셋 카드의 하단 글자가 잘리던 레이아웃도 보정했고, Code 탭에서는 프리셋 영역이 보이지 않도록 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `BuildTopicButtons()`에서 프리셋/기타/추가 카드 높이를 `116`으로 키우고 설명 `MaxHeight`도 늘려 카드 하단 텍스트가 잘리지 않게 했습니다. +- 같은 메서드에서 Code 탭일 때는 `TopicButtonPanel`과 `TopicPresetScrollViewer`를 바로 숨기고, 빈 상태 문구도 `코드 작업을 입력하세요` 기준으로 바꿔 프리셋 기능이 안 보이도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:37 (KST) +- AX Agent 내부 설정 오버레이의 콤보박스도 기본 WPF 형태 대신, `트레이 아이콘 → 설정` 쪽 커스텀 콤보 스타일을 기준으로 맞췄습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `OverlayComboBoxToggle`, `OverlayComboBox`, `OverlayComboBoxItem` 리소스를 추가하고, 서비스/모델 포함 오버레이 콤보들이 같은 토글 버튼형 드롭다운과 항목 호버 스타일을 쓰도록 바꿨습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:40 (KST) +- AX Agent 내부 설정 오버레이에도 메인 설정처럼 `?` 도움말 배지를 복구해, 항목별 상세 설명을 마우스 오버로 바로 볼 수 있게 했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `OverlayHelpBadge` 스타일을 추가하고, `서비스`, `모델`, `기본 서버 주소`, `API 키`, `테마 스타일`, `테마 모드`, `문서 형태`, `디자인 스타일`, `운영 모드`, `폴더 데이터 활용`, `압축 시작 한도`, `최대 컨텍스트 토큰`, `오류 재시도`, `최대 Agent Pass`에 각각 개별 툴팁 설명을 연결했습니다. +- 같은 파일의 고급/개발자 영역에도 `자동 대화 압축`, `확장 스킬 사용`, `실행 전후 자동 확장`, `입력 보정 반영`, `권한 변경 반영`, `Cowork 결과 검토`, `Code 결과 검토`, `도구 병렬 실행`, `프로젝트 규칙 자동 반영`, `에이전트 메모리 사용`, `Plan/Worktree/Team/Cron 도구` 항목별 `?` 설명을 추가해 AX Agent 내부 설정만 보고도 역할을 바로 이해할 수 있게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:43 (KST) +- AX Agent 내부 설정 오버레이의 탭 구조를 예전 버전 기준으로 다시 확장했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 좌측 네비를 `공통 / 채팅 / 코워크/코드 / 코워크 / 코드 / 개발자 / 도구 / 스킬/차단`으로 복구하고, `스킬/차단` 탭 안에 `차단 경로 패턴`, `차단 확장자`, `스킬 설정`, `로드된 스킬`, `폴백 모델`, `MCP 서버`, `등록된 도구/커넥터` 패널을 다시 배치했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에는 오버레이 전용 `RefreshOverlayEtcPanels()`, 차단 목록 렌더링, 스킬 목록 렌더링, 폴백 모델 요약, MCP 서버 카드, 도구 레지스트리 목록 빌더를 추가하고, `코워크/코드` 공통 탭 분기와 `슬래시 팝업 표시 개수`, 드래그앤드롭 AI 액션 저장 경로도 연결했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:51 (KST) +- AX Agent 창 우측 상단의 최소화/최대화/닫기 버튼도 사용자 가이드 상단바 쪽과 비슷한 밀도로 다시 정리했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `TitleBarActionButton`, `TitleBarCloseButton` 스타일을 추가해 버튼 크기를 `40x40`으로 키우고, 간격을 넓히고, 마우스 오버 시 살짝 커지는 스케일 애니메이션과 배경 피드백이 보이도록 조정했습니다. +- 같은 위치에서 AX Agent 상단 창 버튼 3개가 일반 `GhostBtn` 대신 새 타이틀바 전용 스타일을 쓰도록 바꿔, 아이콘 크기와 클릭 영역이 더 명확하게 보이도록 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 08:54 (KST) +- AX Agent의 Chat/Cowork/Code 탭이 서로 다른 폭으로 보이던 채팅 본문/입력 영역 레이아웃도 공통 폭 기준으로 다시 맞췄습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 빈 상태 영역과 하단 컴포저 래퍼를 `HorizontalAlignment="Stretch"` 기준으로 바꾸고 `MaxWidth`를 `1280`으로 통일해, 탭별 내용물 길이에 따라 입력창이 좁아지지 않게 조정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `GetMessageMaxWidth()`도 새 `ComposerShell` 폭을 우선 기준으로 쓰도록 바꿔, Chat/Cowork/Code 메시지 카드와 스트리밍 컨테이너가 같은 레이아웃 폭 안에서 렌더되도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 09:02 (KST) +- AX Agent가 시작 직후 `System.Windows.FrameworkElement.Style` 예외로 죽던 문제도 함께 수정했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 `OverlayComboBox` 스타일이 뒤에서 선언된 `OverlayComboBoxItem`을 `StaticResource`로 먼저 참조하고 있어 런타임에 `MS.Internal.NamedObject` 캐스팅 예외가 발생했는데, 이를 `DynamicResource`로 바꿔 창 초기화가 정상 진행되도록 복구했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 09:06 (KST) +- `build.bat` 실행 시 AX Copilot이 켜져 있으면 애매하게 종료되거나 publish가 꼬이던 흐름도 정리했습니다. +- [build.bat](/E:/AX%20Copilot%20-%20Codex/build.bat) 의 프로세스 정리 루틴을 `taskkill` 1회 호출에서 `정상 종료 시도 → taskkill /T /F → 실제 종료 확인` 순서로 바꾸고, 종료되지 않으면 빌드를 즉시 실패시키도록 수정했습니다. +- 특히 현재 배치 권한보다 높은 권한으로 AX Copilot이 떠 있는 경우에는 무리하게 진행하지 않고 `Access may be denied or the app may be running with higher privileges.` 메시지로 원인을 바로 알 수 있게 했습니다. +- 검증: `cmd /c build.bat` 실행 시, 실행 중인 AX Copilot 프로세스가 권한 문제로 종료되지 않을 때 즉시 실패 처리 확인 +- 업데이트: 2026-04-05 09:13 (KST) +- AX Agent 내부 설정의 `도구` / `스킬·차단` 탭도 메인 설정에 남아 있던 세부 항목을 더 흡수하고, 목록이 너무 길던 부분을 접기/펼치기 구조로 다시 정리했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 `도구` 탭에 훅 실행 타임아웃, 등록된 훅 목록, 훅 추가/편집/삭제 UI를 AX Agent 내부 설정으로 옮겼고, `스킬/차단` 탭에는 스킬 폴더 선택/열기, 슬래시 핀 최대 개수, 슬래시 최근 최대 개수까지 같이 옮겼습니다. +- 스킬 목록은 `/스킬명` 형식으로 표기를 바꾸고 설명은 기존 한국어 설명을 유지했으며, `직접 호출 / 자동·조건부 / 현재 사용 불가` 섹션으로 접기/펼치기 형태로 나눴습니다. 도구 목록도 카테고리별 접기/펼치기로 바꿔 한 번에 너무 길게 보이지 않게 정리했습니다. +- 남아 있던 AX Agent 전용 잔여 설정 중 `PDF 내보내기 기본 경로`, `이미지 입력 활성화`, `코드 리뷰 도구 활성화`도 내부 설정의 `채팅`/`코드` 탭에 재배치해 메인 설정 의존을 줄였습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 09:27 (KST) +- AX Agent 내부 설정의 `개발자` 탭에도 메인 설정에 남아 있던 잔여 운영 항목을 더 흡수해, 실행 이력과 감사/병렬 관련 설정을 오버레이 안에서 바로 조정할 수 있게 했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `호출 간 딜레이(초)`, `서브에이전트 최대 수`, `실행 이력 상세도`, `계획 diff 심각도(개수/비율)`, `워크플로우 시각화`, `전체 호출·토큰 합계 표시`, `감사 로그`, `감사 로그 폴더 열기` 행을 추가해 `개발자` 탭 안에서 한 번에 볼 수 있도록 재배치했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 해당 값들의 로드/저장/즉시 반영 경로를 AX Agent 오버레이 저장 흐름에 연결하고, `실행 이력 상세도` 콤보, 숫자 입력 검증, 감사 로그 폴더 열기 동작도 함께 붙였습니다. +- 이 변경으로 AX Agent 내부 설정은 `채팅 / 코워크/코드 / 코워크 / 코드 / 개발자 / 도구 / 스킬·차단` 탭 구조를 유지한 채, 메인 설정에 남아 있던 AX Agent 전용 세부값 상당수를 각 기능 탭으로 다시 분산 배치한 상태가 됐습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 09:38 (KST) +- AX Agent 내부 설정의 공통/채팅/코워크 배치도 다시 정리했습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 의 `ApplyAiEnabledState()`에서 메인 설정의 `AX Agent` 탭이 다시 살아나던 경로를 끊어, 일반 설정 화면에서는 AX Agent 탭이 더 이상 보이지 않게 했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 AX Agent 내부 설정의 `AX Agent 사용` 저장 경로를 제거하고 항상 활성 상태로 고정했으며, `서비스/모델`과 운영 모드를 `공통`으로, `문서 형태/디자인 스타일`을 `코워크`로 다시 배치했습니다. +- 같은 위치에서 `최대 컨텍스트 토큰`, `압축 시작 한도(%)`는 숫자 입력 대신 `4K / 16K / 64K / 256K / 1M`, `60 / 70 / 80 / 90%` 프리셋 버튼으로 고를 수 있게 바꾸고 내부 설정 저장과 즉시 반영을 연결했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 09:54 (KST) +- 메인 설정의 `AX Agent` 탭에 남아 있던 `표현 수준` 행도 [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 에서 숨겨, 일반 설정 화면에서 더 이상 보이지 않게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 09:56 (KST) +- AX Agent 채팅 입력창이 입력할수록 과하게 커지고 가로폭도 창 너비를 과도하게 채우던 레이아웃을 고정 폭 기준으로 다시 정리했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 `ComposerShell`을 `Center + Width/MaxWidth 640` 기준으로 바꿔, 입력 박스가 창 너비 전체를 계속 먹지 않도록 조정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `ApplyExpressionLevelUi()`에서 입력창 최대 높이도 `rich 120 / balanced 108 / simple 96`으로 낮춰, 여러 줄 입력 시에도 이전처럼 과하게 길어지지 않게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 09:58 (KST) +- AX Agent Chat 탭에서 Gemini 사용 시 빈 응답/진행 중 멈춤처럼 보이던 현상도 보정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 일반 전송/재생성 흐름에서 Gemini는 스트리밍 대신 비스트리밍 `SendAsync()` 경로를 사용하도록 바꿔, 스트리밍 파싱 문제로 빈 컨테이너만 남는 상황을 우회했습니다. +- 같은 파일에서 메시지 버블 최대폭 계산도 `320~720` 범위로 다시 맞추고, 스트리밍 종료 시 내용이 비어 있으면 `(빈 응답)` 기본 문구로 치환해 완전히 빈 말풍선이 남지 않게 했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:02 (KST) +- AX Agent 채팅창에서 입력 내용이 거의 없는데도 컴포저 높이가 계속 커지고, 빈 assistant 말풍선이 대화 목록에 남는 현상도 추가로 보정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `UpdateInputBoxHeight()`를 추가해 입력창 높이를 실제 줄 수 기준으로 `MinHeight~MaxHeight` 범위에서 직접 고정하고, 넘칠 때만 내부 스크롤이 나오게 바꿨습니다. +- 같은 파일의 `RenderMessages()`에서는 내용이 비어 있는 assistant 메시지를 렌더 대상에서 제외하고, 일반 전송/재생성 완료 직전 `assistantMsg.Content`가 비어 있으면 `(빈 응답)`으로 먼저 확정해 저장/재렌더 때도 빈 카드가 남지 않게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:10 (KST) +- AX Agent 내부 설정의 `채팅` 탭에 잘못 들어가 있던 `테마 스타일`, `테마 모드` 블록도 제거했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 채팅 설정 섹션의 중복 테마 UI를 삭제해, 채팅 탭에는 실제 채팅 관련 항목만 남도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:12 (KST) +- AX Agent 내부 설정의 `공통` 탭에도 메인 설정에만 있던 `등록 모델 관리`를 추가했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 사내 서비스(`Ollama`, `vLLM`) 선택 시 `모델 추가`, `편집`, `삭제`, `선택`이 가능한 등록 모델 관리 패널을 내부 설정 안에 붙였습니다. +- 메인 설정에서 쓰던 [ModelRegistrationDialog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ModelRegistrationDialog.cs) 흐름을 그대로 연결해, 내부 설정에서 추가한 모델도 기존 `RegisteredModels` 저장 경로와 동일하게 저장되도록 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:16 (KST) +- AX Agent 내부 설정의 `압축 시작 한도(%)`도 분류를 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 해당 행(`OverlayAnchorAdvanced`)이 `코워크/코드 공통` 탭에서만 보이도록 바꿔, 개발자/도구/스킬 탭에 섞여 나오지 않게 조정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:19 (KST) +- AX Agent 내부 설정 탭의 제목/설명 위치도 본문 최상단으로 정리했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 상단 전용 헤더를 추가해 `공통 설정` 같은 탭 제목과 설명이 먼저 보이게 했고, 아래쪽 중복 헤더는 숨겼습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:27 (KST) +- Chat 탭에서 엔터 전송 뒤 입력창 높이가 계속 커지고, 토큰은 집계되는데 assistant 메시지가 화면에 안 보이던 문제도 같이 보정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `UpdateInputBoxHeight()`를 조정해 Chat 탭은 입력창 높이를 고정으로 유지하고, Cowork/Code만 명시적 줄 수 기준으로 높이를 늘리게 바꿨습니다. +- 같은 파일에 `SyncLatestAssistantMessage(...)`를 추가하고 응답 완료 뒤 `RenderMessages(preserveViewport: true)`를 다시 태우도록 바꿔, 응답 토큰은 들어왔는데 저장된 assistant 메시지가 비어 보여 렌더가 사라지던 상태를 끊었습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:35 (KST) +- 메인 설정에 남아 있던 AX Agent 진입 흐름을 정리하고, 일반 설정 하단에서 AX Agent 내부 설정을 바로 여는 전용 바로가기를 추가했습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 의 일반 탭 하단에 `AX Agent 설정 바로가기` 카드를 추가해, 설정창 안에서 바로 AX Agent 채팅창과 내부 설정 오버레이를 열 수 있게 했습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 에서 메인 설정의 표시 대상 목록에서 `AX Agent` 탭을 제외하고, 기존 AX Agent 바로가기 버튼도 탭 전환이 아니라 `App.OpenAgentSettingsInChat()` 경로를 타도록 바꿔 메인 설정 잔여 진입을 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:41 (KST) +- AX Agent Chat 탭의 입력창 높이 규칙도 다시 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `UpdateInputBoxHeight()`에서 `Chat` 탭만 높이를 고정하던 분기를 제거해, 이제 `Chat / Cowork / Code` 모두 실제 줄바꿈 문자(`Shift+Enter`)가 있을 때만 높이가 늘어나고 일반 입력/전송만으로는 커지지 않게 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:48 (KST) +- 메인 설정 안에 숨어 있던 AX Agent 옛 UI 잔재 1차도 걷어냈습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 에서 일반 탭 상단의 숨김 `AI 기능`, `운영 모드` 블록과 하단 공용 버튼 바의 중복 `AX Agent 설정` 버튼을 제거해, 메인 설정 내부에 남아 있던 보이지 않는 AX Agent 진입 잔재를 줄였습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 의 `ApplyAiEnabledState()`와 `ApplyOperationModeState()`도 이에 맞춰 숨김 컨트롤 동기화 코드를 걷어내고, 메인 설정에서는 더 이상 그 컨트롤들을 전제로 동작하지 않게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:02 (KST) +- AX Agent 채팅/코워크/코드 전송 안정화도 같이 보정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 `InputBox.Text`를 비우거나 대기열 메시지를 다시 넣는 지점마다 `UpdateInputBoxHeight()`를 즉시 호출하도록 바꿔, 전송 뒤 입력창 높이가 남은 상태로 계속 커져 보이던 문제를 줄였습니다. +- 같은 파일의 `SendMessageAsync()`는 Chat 탭에서 스트리밍 대신 비스트리밍 응답을 우선 사용하도록 바꿔, 토큰은 집계되는데 본문이 비거나 늦게 반영되던 흐름을 안정화했습니다. +- Cowork/Code 탭은 응답 완료 후 assistant 본문이 비어 있으면 최근 실행 이벤트 요약을 최종 응답으로 보강하고, `ShowExecutionHistory`를 기본적으로 내려 실행 로그 잔상이 본문을 덮어 보이지 않게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:08 (KST) +- AX Agent 내부 설정의 `도구`, `스킬/차단` 탭 접기 카드도 처음엔 모두 닫힌 상태로 열리게 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 로드된 스킬 섹션, 등록 도구/커넥터 카테고리 섹션, 등록 훅 섹션의 `CreateOverlayCollapsibleSection(...)` 기본 확장값을 모두 `false`로 바꿔, 내부 설정 진입 시 긴 목록이 한꺼번에 펼쳐지지 않게 했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 10:23 (KST) +- AX Agent 실행 경로를 `claw-code` 기준으로 한 단계 더 분리했습니다. +- [AxAgentExecutionEngine.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs) 에 프롬프트 스택 조합, 실행 모드 판정, 최종 assistant 메시지 커밋을 모았고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `SendMessageAsync()`는 이 엔진을 통해 `Chat / Cowork / Code` 전송 메시지를 준비하도록 정리했습니다. +- 같은 파일에 `RunAgentLoopAsync(...)`를 추가해 Cowork/Code의 중복된 에이전트 루프 실행 분기를 한 경로로 합쳤고, 완료 알림과 이벤트 핸들러 해제도 같은 패턴으로 묶었습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:11 (KST) +- AX Agent 입력창 높이 계산과 내부 설정 숫자 입력 방식도 다시 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `UpdateInputBoxHeight()`를 수동 `Height` 고정 방식에서 `MinLines / MaxLines` 기반 자동 높이 방식으로 바꿔, `Shift+Enter` 줄바꿈이 있을 때만 자연스럽게 늘어나고 빈 상태에서 높이가 누적돼 남는 현상을 줄였습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 `Temperature`, `오류 재시도`, `최대 Agent Pass`, `호출 간 딜레이`, `서브에이전트 최대 수`는 텍스트 입력 대신 슬라이더와 현재값 배지로 바꿨고, `Temperature`와 `최대 Agent Pass`는 개발자 탭에서만 보이도록 다시 분류했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:11 (KST) +- 메인 설정에서 AX Agent 진입 위치도 좌측 사이드바로 옮겼습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 의 일반 탭 맨 아래에 있던 `AX Agent 설정 바로가기` 카드는 제거하고, 좌측 `MainSettingsTab`에 `AX Agent` 전용 네비 항목을 추가했습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 에서 이 새 사이드바 항목 선택 시 기존과 동일하게 AX Agent 채팅창과 내부 설정 오버레이를 바로 열도록 연결했고, 구형 숨김 탭만 가리도록 가시성 로직도 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:11 (KST) +- AX Agent 내부 설정의 남아 있던 숫자 입력 잔여 항목도 예전 설정창 패턴에 맞춰 슬라이더형으로 계속 이식했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml), [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 `도구 훅 스크립트 제한 시간`, `슬래시 팝업 표시 개수`, `슬래시 핀 최대 개수`, `슬래시 최근 최대 개수`를 텍스트박스 대신 슬라이더 + 현재값 배지 구조로 바꾸고, 숨김 텍스트 필드는 저장 호환용으로만 유지했습니다. +- 같은 파일의 오버레이 동기화 경로(`RefreshOverlayVisualState`, `RefreshOverlayEtcPanels`)에도 해당 값들의 슬라이더/배지 동기화를 추가해, 섹션 전환이나 재오픈 후에도 값이 바로 맞춰 보이도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:22 (KST) +- 메인 설정에 남아 있던 구형 AX Agent 탭 본문도 실제 탭 컬렉션에서 제거해, 숨김 상태로 남아 있던 레거시 경로가 다시 선택되지 않도록 정리했습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 생성자에서 `AgentTabItem`을 `MainSettingsTab.Items`에서 제거하고, `MainSettingsTab_SelectionChanged()`는 좌측 바로가기용 `AgentShortcutTabItem`만 AX Agent 내부 설정 오버레이로 라우팅하도록 단순화했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:24 (KST) +- 구형 AX Agent 본문이 로드 시점에 초기화되던 경로도 추가로 끊었습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 에 `HasLegacyAgentTab()` 가드를 넣어, `MoveBlockSectionToEtc()`, `BuildServiceModelPanels()`, `BuildToolRegistryPanel()`, `LoadAdvancedSettings()`, `SyncAgentSelectionCards()`, `ApplyAgentSubTabVisibility()`가 실제 구형 AX Agent 탭이 컬렉션에 남아 있을 때만 실행되게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:25 (KST) +- 원래 설정에 있던 도구별 사용 토글도 AX Agent 내부 설정 `도구` 탭으로 옮겼습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `BuildOverlayToolRegistryPanel()`에서 각 도구 카드 우측에 `ToggleSwitch`를 붙여, 카테고리별 접기 섹션 안에서 바로 도구 사용 여부를 바꿀 수 있게 했습니다. +- 도구 토글은 기존과 동일하게 `Llm.DisabledTools` 저장 경로를 그대로 사용하고, 변경 즉시 내부 설정 상태를 저장한 뒤 목록을 다시 그려 현재 상태가 바로 보이게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:27 (KST) +- 작업 지침에도 설정 입력 UI 통일 규칙을 추가했습니다. +- [AGENTS.md](/E:/AX%20Copilot%20-%20Codex/AGENTS.md) 의 `설정 UI 패턴` 섹션에 `on/off`는 `ToggleSwitch`, 숫자 입력은 기존 슬라이더 + 현재값 배지 패턴을 우선 사용하고, 메인 설정과 AX Agent 내부 설정 간 표현 방식도 통일해야 한다는 규칙을 명시했습니다. +- 업데이트: 2026-04-05 11:28 (KST) +- 시작 직후 나던 AX Agent 프리워밍 예외도 수정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `TryGetOverlayLlmSettings()` 가드를 추가하고, 내부 설정 슬라이더 `ValueChanged` 핸들러들이 초기화 중 `_settings.Settings.Llm` 이 준비되지 않았을 때는 즉시 빠지도록 정리했습니다. +- 원인은 프리워밍 중 `SldOverlayMaxAgentIterations_ValueChanged`가 너무 일찍 발화하면서 null 경로를 건드리던 것이었고, 같은 유형이 다른 슬라이더에도 생기지 않도록 공통 방어로 같이 막았습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:31 (KST) +- 런처도 `Agent Compare` 기준으로 빠진 기능을 다시 이식하기 시작했습니다. +- [LauncherViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/LauncherViewModel.cs), [LauncherViewModel.LauncherExtras.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/LauncherViewModel.LauncherExtras.cs), [LauncherWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.xaml), [LauncherWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.xaml.cs), [LauncherWindow.Shell.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.Shell.cs)에 `빠른 실행 칩`, `검색 히스토리 위/아래 탐색`, `선택 항목 미리보기 패널`, `F3 QuickLook`, `F4 OCR`, 하단 `위젯 바`를 현재 런처 흐름에 맞게 다시 연결했습니다. +- [QuickActionChip.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/QuickActionChip.cs), [SearchHistoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/SearchHistoryService.cs), [QuickLookWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/QuickLookWindow.xaml), [QuickLookWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/QuickLookWindow.xaml.cs) 도 새로 추가해, `Agent Compare` 쪽 런처 보조 기능이 현재 앱에서도 독립적으로 동작할 수 있도록 했습니다. +- [UsageRankingService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/UsageRankingService.cs) 에는 빠른 실행 칩 생성을 위한 `GetTopItems()`를 추가해, 최근 많이 쓴 경로를 런처 입력창 아래에서 바로 다시 열 수 있게 했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 +- 업데이트: 2026-04-05 11:58 (KST) --- diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index f159034..997f98d 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,5 +1,19 @@ # AX Copilot - 媛쒕컻 臾몄꽌 +- Document update: 2026-04-05 07:11 (KST) - Simplified the AX Agent footer for Cowork/Code by removing the duplicated `MoodIconPanel` chip group from those tabs and leaving workspace context only in the main folder path row. Also removed the outline border from the data-usage button so the footer option strip reads flatter and less pill-heavy. +- Document update: 2026-04-05 07:08 (KST) - Improved AX Agent responsiveness in three hot paths: added an ordered meta cache in `ChatStorageService` so repeated conversation-list refreshes stop re-sorting the full meta set every time, short-circuited `SaveConversationSettings()` when permission/data-usage/mood/output-format values are unchanged, and debounced the sidebar conversation search refresh to avoid re-filtering on every keystroke. +- Document update: 2026-04-05 02:00 (KST) - Reworked the AX Agent in-chat gear overlay navigation itself to match the restored internal settings taxonomy: `basic / chat / cowork / code / dev / tools / skill-block`. The left nav labels now follow that scheme, and the overlay rows/toggles are regrouped per tab instead of the earlier `common / service / permission / advanced` split. +- Document update: 2026-04-05 01:46 (KST) - Corrected the AX Agent settings migration target after verifying that the in-chat gear icon opens `AgentSettingsOverlay` inside `ChatWindow`, not the standalone `AgentSettingsWindow`. Moved the missing advanced controls into the live overlay surface: project-rules toggle, agent-memory toggle, max agent iterations, and code tool toggles for plan/worktree/team/cron. +- Document update: 2026-04-05 01:40 (KST) - Extended the in-chat AX Agent settings window behind the internal gear icon so it now owns additional settings that previously remained in the main settings surface or quick overlay: default output format, default mood, project-rules toggle, agent-memory toggle, max agent iterations, and code tool toggles for plan/worktree/team/cron. +- Document update: 2026-04-05 01:40 (KST) - Reduced the bottom composer's capsule feel by tightening the corner radius on the footer option buttons, token usage card, glow border, and input container so the AX Agent footer reads as a denser rectangular control row. +- Document update: 2026-04-05 01:35 (KST) - Changed the AX Agent Cowork conversation filter from workspace/project grouping to task-type grouping so the sidebar dropdown, current label, and list filtering all follow the preset/category model used by Cowork topic cards. +- Document update: 2026-04-05 01:35 (KST) - Kept the Code tab on its prior workspace-based project filter to avoid collapsing code conversations into the Cowork task-type taxonomy. +- Document update: 2026-04-05 01:22 (KST) - Moved more of the hidden AX Agent settings surface into the in-chat internal settings window: tool exposure cards, hook management, skills folder path, slash popup size, drag-drop AI toggles, fallback model selection, and MCP server management are now available directly there. +- Document update: 2026-04-05 01:22 (KST) - Reduced the overly pill-shaped data-usage control in the chat bottom bar by switching its capsule border to a rounded rectangle, keeping the footer visuals closer to the rest of the AX Agent option row. +- Document update: 2026-04-05 01:15 (KST) - Audited the AX Agent internal settings save flow and removed direct mutation of the live settings object during pre-save tab interaction. Service/theme/display selections now stay local to the window until explicit save. +- Document update: 2026-04-05 01:15 (KST) - Restored persistence for the internal AX Agent window's missing save surfaces: `VllmAllowInsecureTls`, active-service model sync, and compatibility writes back to `Llm.Model` for the runtime resolver. +- Document update: 2026-04-05 01:12 (KST) - Restored the AX Agent in-chat settings classification toward the older hidden-tab layout. The internal settings window now follows the earlier `basic / chat / cowork / code / dev / tools / skill-block` navigation baseline instead of only the later flattened grouping. +- Document update: 2026-04-05 01:12 (KST) - Reconnected `AiEnabled` and `AgentUiExpressionLevel` persistence from the AX Agent internal settings window, and removed the normalization path that forcibly re-enabled AI after save so the restored toggle can persist again. - Document update: 2026-04-05 00:58 (KST) - Compared the launcher implementation under `Agent Compare/AX Copilot` with the current AX Commander and imported the missing launcher handler set into the live app registration flow, excluding only the compare app's AI-coupled launcher handlers. - Document update: 2026-04-05 00:58 (KST) - Added launcher-side support files for the imported feature set: quick-link/session/schedule/macro/SSH settings models, scheduler/tag/notification-history/icon-cache/url-template/pomodoro services, editor windows, launcher position persistence fields, and QR/OCR build dependencies. - Document update: 2026-04-05 00:52 (KST) - Normalized the composer/footer wording so model, data-usage, permission, and Git branch surfaces read in the same status language instead of mixing short labels and raw values. @@ -4112,3 +4126,349 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - ?섎룞 `/compact`?€ ?꾩넚 ???먮룞 而⑦뀓?ㅽ듃 ?뺤텞??紐⑤몢 媛숈? `RecordCompactionStats(...)` 寃쎈줈瑜??ъ슜?섎룄濡?留욎떠, ?뺤텞 寃곌낵媛€ ?쇳쉶???곹깭 硫붿떆吏€?먮쭔 癒몃Ъ吏€ ?딄퀬 UI?먯꽌 ?ъ갭議?媛€?ν븯?꾨줉 ?뺣━?덈떎. - ?대쾲 ?쇱슫?쒕???`claude-code`??compact ?뚯뒪瑜?湲곗??쇰줈 `session memory compaction ??microcompact ??context collapse/snip ??autocompact ??post-compaction 怨꾩륫` ?먮쫫??AX 湲곗?怨?鍮꾧탳 遺꾩꽍??以€鍮꾨? 吏꾪뻾?덈떎. - 寃€利? dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\ (寃쎄퀬 0 / ?ㅻ쪟 0) + +### 2026-04-05 추가 진행 기록 (설정 메뉴 단순화) +- 업데이트: 2026-04-05 07:17 (KST) +- 별도 `AX Agent 설정` 창에서 `AX Agent 사용`, `표현 수준` 항목을 숨겨 기본 탭 상단에 불필요한 전역 토글과 표현 레벨 카드가 보이지 않도록 정리했습니다. +- AX Agent 내부 설정 오버레이에서도 `AX Agent 사용` 행을 숨겨, 톱니 설정 메뉴가 실제 작업 옵션 위주로 보이도록 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (스킬 목록 복구) +- 업데이트: 2026-04-05 07:24 (KST) +- `Agent Compare` 기준으로 메인 `설정 > AX Agent`에만 남아 있던 스킬 목록 표시 흐름을 다시 확인하고, 별도 `AX Agent 설정` 창의 `스킬/차단` 탭에 로드된 스킬 카드 목록을 복구했습니다. +- 스킬 목록은 `SkillService`에서 읽은 현재 스킬을 내장/고급 그룹으로 나눠 보여주고, 저장 시 스킬 폴더 경로 기준으로 재로드해 즉시 반영되도록 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (트레이 메뉴 오픈 지연 완화) +- 업데이트: 2026-04-05 07:39 (KST) +- `Agent Compare`의 트레이 메뉴 표시 경로와 현재 구현을 비교해, 현재판에서 메뉴를 띄울 때마다 `Show()` 후 `UpdateLayout()`로 실제 크기를 다시 확정하던 지점을 병목으로 확인했습니다. +- `TrayMenuWindow`는 메뉴 항목이 바뀔 때만 크기 캐시를 무효화하고, 우클릭 직전에는 보이기 전에 `Measure/Arrange`로 크기만 계산한 뒤 바로 위치를 잡아 표시하도록 변경했습니다. +- `App`에서는 트레이 메뉴 생성 직후 `ApplicationIdle` 우선순위로 `PrepareForDisplay()`를 호출해 첫 우클릭 이전에 레이아웃을 한 번 미리 준비하게 했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 설정 뒤로가기 위치 정리) +- 업데이트: 2026-04-05 07:45 (KST) +- AX Agent 내부 설정 오버레이의 `뒤로가기` 버튼이 오른쪽 본문 헤더에 따로 떠 있어 패널 접기/닫기 동작처럼 보이던 배치를 정리했습니다. +- 왼쪽 설정 네비 상단의 `설정 / AX Agent` 제목을 화살표가 붙은 하나의 뒤로가기 헤더 버튼으로 바꾸고, 기존 오른쪽 상단 버튼은 제거했습니다. +- 본문 `ScrollViewer`는 헤더 빈 줄 없이 바로 시작하도록 `Grid.RowSpan`과 패딩을 조정해 설정 진입 시 시선 흐름이 끊기지 않게 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (사이드바 접기 버튼 Claude형 정리) +- 업데이트: 2026-04-05 07:49 (KST) +- AX Agent 상단 헤더 왼쪽의 사이드바 토글 버튼을 기존 큰 MDL2 아이콘 기반 고스트 버튼에서, 작은 라운드 사각 안에 3줄 메뉴가 들어간 Claude 계열 토글로 교체했습니다. +- 호버 시에는 외곽선과 선 색만 액센트로 바뀌도록 제한해 상단 바가 과하게 튀지 않게 했고, 버튼 크기도 30px 기준으로 맞춰 헤더 밀도를 정리했습니다. +- 코드비하인드에서 열림/닫힘마다 `ToggleSidebarIcon.Text`를 바꾸던 처리도 제거해, 새 버튼 구조와 상태 관리가 충돌하지 않도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (Chat 상단 여백 및 압축 UI 분리) +- 업데이트: 2026-04-05 07:54 (KST) +- Chat 빈 상태 화면의 대표 아이콘이 상단 탭 메뉴와 너무 가까워 보이던 배치를 조정하기 위해, `EmptyState` 상단 스택의 마진을 늘려 시각 충돌을 줄였습니다. +- `RefreshContextUsageVisual()`에 탭 분기를 추가해 Chat에서는 `TokenUsageCard`를 바로 숨기고, Cowork/Code에서만 컨텍스트 사용량과 `압축` 버튼이 보이도록 정리했습니다. +- 이 변경으로 Chat 입력 하단은 더 단순해지고, 에이전트 작업 성격이 강한 Cowork/Code에서만 토큰 압축 UI가 유지됩니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (런처 하단 상태 문구 자동 숨김 보정) +- 업데이트: 2026-04-05 08:00 (KST) +- 런처 하단 `IndexStatusText`가 사라지지 않고 남는 사례를 추적한 결과, 인덱스 상태 표시와 토스트 오버레이가 같은 `_indexStatusTimer`를 공유해 서로의 자동 숨김 타이머를 덮어쓸 수 있는 구조를 확인했습니다. +- `LauncherWindow`에 `_toastTimer`를 분리하고, 인덱스 재구축 시작/완료 문구는 `ShowIndexStatus(...)` 공통 경로로 묶어 상태 문구가 독립적으로 일정 시간 후 사라지도록 수정했습니다. +- 이 변경으로 캡처/복사/즐겨찾기 토스트가 뜨는 동안에도 하단 색인 상태 문구의 숨김 타이밍이 꼬이지 않습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (배포 스크립트 경로 정리 및 보호 상태 명시) +- 업데이트: 2026-04-05 08:14 (KST) +- 루트 [build.bat](/E:/AX%20Copilot%20-%20Codex/build.bat)을 전면 정리해, 현재 작업 디렉터리에 따라 상대 경로가 꼬이던 `dist`, `payload.zip`, 인스톨러 exe 복사 경로를 모두 `%~dp0` 기준 절대 경로로 고정했습니다. +- 배포 순서는 `AxCopilot publish -> 난독화 도구 확인 -> AxKeyEncryptor publish -> payload.zip 생성 -> AxCopilot.Installer build -> dist 메타데이터 정리`로 고정했고, 배치 파일은 `pause` 없이 자동 실행 환경에서도 종료되도록 수정했습니다. +- 보호 상태를 점검한 결과, 현재 레포에는 ConfuserEx / Dotfuscator / Babel / Eazfuscator / SmartAssembly 같은 외부 난독화 도구 연결이 없고, `Security/ObfuscationAttributes.cs`와 Release 메타데이터 제거 설정만 존재합니다. +- 따라서 현재 배포본은 `PDB/XML/debug metadata 제거` 수준의 보강만 적용되며, 진짜 디컴파일 방지는 아직 미구성 상태임을 스크립트 경고로 명확히 출력하게 했습니다. +- 검증: `cmd /c build.bat` 실행 기준 메인 앱 publish, `payload.zip` 생성, `AxCopilot.Installer` 빌드, `dist\AxCopilot_Setup.exe` 복사, dist 정리까지 정상 완료 + +### 2026-04-05 추가 진행 기록 (AX Agent 공통 항목 숨김 및 도움말 툴팁 복구) +- 업데이트: 2026-04-05 07:59 (KST) +- 메인 `설정 > AX Agent > 공통` 화면에서 사용자가 반복 요청한 `AX Agent 사용`, `표현 수준` 행이 실제로 남아 있던 경로를 다시 확인하고, 해당 `AgentSettingsRow` 두 줄을 `Visibility="Collapsed"`로 숨겼습니다. +- `SettingsWindow.xaml.cs`와 `SettingsService.cs`에서는 `AgentUiExpressionLevel`을 `rich`로 강제 정규화해, UI에서 표현 수준을 노출하지 않아도 내부 설명 밀도는 항상 `풍부하게` 기준으로 유지되도록 맞췄습니다. +- `SettingsWindow.xaml`의 `HelpTooltipStyle`은 라이트 테마에서 흰 글자가 옅은 배경에 묻히던 문제를 피하도록 툴팁 배경을 진한 남색 계열로 고정하고, 전경색/텍스트 전경색을 흰색으로 통일했습니다. +- 이 변경으로 `응답 설정 > Temperature`, `PDF 내보내기 기본 경로`, `팁 알림 표시 시간`, 코드/도구/스킬 관련 `?` 도움말 라벨이 라이트/다크 테마 모두에서 같은 대비로 보입니다. + +### 2026-04-05 추가 진행 기록 (런처 마지막 위치 기억 복구) +- 업데이트: 2026-04-05 07:59 (KST) +- `Agent Compare`와 현재판 설정/런처 코드를 대조해 빠져 있던 `Launcher.RememberPosition` 바인딩을 `SettingsViewModel`과 `SettingsWindow`에 다시 연결했습니다. +- `LauncherWindow`는 표시 직전 저장된 좌표가 유효한 화면 범위 안에 있으면 그 위치를 복원하고, 창 이동 중에는 메모리 캐시만 갱신한 뒤 숨겨질 때 설정 파일에 마지막 좌표를 저장하도록 정리했습니다. +- 이 변경으로 `AX Commander`를 닫은 자리에서 다시 여는 비교본 동작이 현재판에서도 되살아났고, 드래그 중 매 프레임 저장하던 식의 불필요한 디스크 쓰기도 피했습니다. + +### 2026-04-05 추가 진행 기록 (AX Agent 열기 실패 직접 원인 수정) +- 업데이트: 2026-04-05 08:02 (KST) +- 앱 로그(`%APPDATA%\AxCopilot\logs\app-2026-04-05.log`)를 확인한 결과, AX Agent 열기 실패의 직접 원인은 `ChatWindow` 초기화 시 `HelpTooltipStyle`을 찾지 못해 발생한 `XamlParseException`이었습니다. +- `ChatWindow.xaml`에는 `HelpTooltipStyle`을 참조하는 `?` 툴팁이 존재했지만, 해당 스타일은 메인 설정 창/별도 설정 창 내부에만 정의되어 있어 AX Agent 창 단독 생성 시 리소스 조회가 실패하고 있었습니다. +- [App.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/App.xaml)에 동일 스타일을 전역 리소스로 추가해, AX Agent/설정/오버레이가 공통으로 같은 툴팁 스타일을 찾도록 수정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (도구/스킬 설명창을 AX Agent 내부 설정으로 복구) +- 업데이트: 2026-04-05 08:08 (KST) +- 메인 `설정 > AX Agent`에만 남아 있던 도구/스킬 설명 성격의 안내 블록이 AX Agent 내부 오버레이에는 비어 있던 상태를 확인하고, `ChatWindow` 내부 설정 오버레이에 설명 패널을 추가했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml)의 `OverlayToolsInfoPanel`에는 훅 동작 흐름, 활용 예시, 안전 설계를 요약한 설명을 넣었고, `OverlayEtcInfoPanel`에는 스킬 파일 형식, 기본 폴더 경로, MCP/폴백 모델/드래그 드롭 관리 범위를 안내하는 설명을 복구했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `SetOverlaySection()`에서는 `도구` 탭일 때 도구 설명창만, `스킬/차단` 탭일 때 스킬 설명창만 보이도록 분기해 기존 탭 흐름을 유지했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정 순환 버튼을 커스텀 콤보박스로 전환) +- 업데이트: 2026-04-05 08:15 (KST) +- AX Agent 내부 설정 오버레이에서 `Fast`, `의사결정 수준`, `실행 전 계획`, `권한 모드`, `기본 출력 형식`, `테마 스타일`, `운영 모드`, `폴더 데이터 활용`처럼 텍스트가 순환하던 버튼형 입력을 커스텀 콤보박스 기반으로 바꿨습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml)에 오버레이 전용 `OverlayComboBox` 스타일과 각 선택 항목용 `ComboBox`를 배치해, AX Agent 내부 설정이 claw-code식 순환 버튼보다 바로 값을 고르기 쉬운 구조가 되도록 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서는 선택값 동기화 헬퍼(`SelectComboTag`, `PopulateOverlayMoodCombo`)와 오버레이 전용 `SelectionChanged` 핸들러를 추가해 기존 저장 경로를 그대로 재사용하도록 연결했습니다. +- `운영 모드`는 외부 모드로 바꿀 때 기존과 동일하게 비밀번호 확인을 유지하고, `권한 모드`는 글로벌 권한과 기본 에이전트 권한을 함께 맞추도록 저장 경로를 통일했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (코워크 좌측 필터 라벨 정리) +- 업데이트: 2026-04-05 08:18 (KST) +- 코워크 탭 좌측 패널의 상단 필터는 이미 작업 유형 기준으로 동작하고 있었지만, `SidebarCoworkMenu` 라벨만 예전 `워크스페이스` 문구가 남아 있어 의미가 어긋나 있었습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 코워크 메뉴 라벨을 `작업 유형`으로 바꿔, 실제 필터 동작과 표기 문구가 일치하도록 정리했습니다. +- Code 탭은 계속 워크스페이스 기준 필터를 사용하므로 기존 `워크스페이스` 표기를 유지했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (입력창 비대화 및 입력 중 깜빡임 완화) +- 업데이트: 2026-04-05 08:24 (KST) +- Code/Cowork 채팅 입력창에서 텍스트 입력 시 입력 영역이 비정상적으로 세로로 길어지고, 매 타이핑마다 컨텍스트/대기열 UI가 즉시 다시 그려지면서 화면이 번쩍이는 현상을 정리했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 입력 영역 `Grid.RowDefinition` 마지막 행을 `*`에서 `Auto`로 바꾸고 `InputBox`를 `VerticalAlignment="Top"`으로 고정해, 입력 행이 남는 세로 공간을 끌어먹지 않도록 수정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `_inputUiRefreshTimer`를 추가하고 `InputBox_TextChanged()`에서는 무거운 `RefreshContextUsageVisual()` / `RefreshDraftQueueUi()`를 즉시 실행하지 않고 90ms 디바운스 후 반영하도록 변경했습니다. +- 이 변경으로 입력 즉시성은 유지하면서도, 토큰 사용량 카드와 대기열 카드의 재구성 빈도를 낮춰 입력 중 깜빡임과 렌더 흔들림을 줄였습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (모델 선택 팝업 중복 제어 제거) +- 업데이트: 2026-04-05 08:28 (KST) +- 모델/추론 빠른 설정 팝업 하단에 모델 리스트 아래 한 번 더 반복되던 모델 칩과, 다른 위치에서 이미 조정 가능한 `계획`, `권한` 빠른 버튼이 중복으로 노출되고 있었습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 `InlineModelChipPanel`을 숨기고 `BtnInlinePlanMode`, `BtnInlinePermission`을 `Collapsed` 처리해, 팝업이 서비스/모델/추론 중심으로 더 단순하게 보이도록 정리했습니다. +- 팝업 상단 힌트 문구도 실제로 남아 있는 기능 기준에 맞춰 `서비스, 모델, 추론을 여기서 바로 바꿉니다`로 수정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (프리셋 영역 스크롤바 자동 표시화) +- 업데이트: 2026-04-05 08:31 (KST) +- AX Agent 채팅 빈 상태 화면의 프리셋 카드 영역은 내용이 충분히 적어도 세로 스크롤바 자리와 여백이 고정처럼 보이는 상태였습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 프리셋 영역 `ScrollViewer`에 `TopicPresetScrollViewer` 이름을 부여하고 기본 `VerticalScrollBarVisibility`를 `Disabled`로 조정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `UpdateTopicPresetScrollMode()`를 추가해, 프리셋 버튼 재구성 후와 창 크기 변경 시 `ExtentHeight`와 `ViewportHeight`를 비교하고 실제로 넘칠 때만 세로 스크롤과 우측 패딩을 활성화하도록 만들었습니다. +- 이 변경으로 프리셋 카드가 정상 높이 안에 들어올 때는 스크롤바가 보이지 않고, 작은 창이나 프리셋이 많아지는 경우에만 자동으로 나타납니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (후속 요청 입력 미리보기 제거) +- 업데이트: 2026-04-05 08:34 (KST) +- AX Agent 입력창 위 `후속 요청` 카드는 사용자가 타이핑 중인 현재 입력을 실시간으로 그대로 미리 보여주고 있었는데, 실제 큐 적재 전에는 불필요한 중간 표시가 되는 상태였습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `RefreshDraftQueueUi()`에서 `DraftPreviewCard`를 상시 숨기고, 후속 요청은 엔터 입력 후 `QueueComposerDraft(...)`로 실제 큐에 적재된 다음 아래 대기열 목록에만 반영되도록 정리했습니다. +- 이 변경으로 타이핑 중 현재 입력이 상단 카드에 중복 표시되지 않고, 후속 요청은 실제로 쌓였을 때만 큐 UI에서 확인할 수 있습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (프리셋 카드 글자 잘림 보정 및 Code 탭 프리셋 숨김) +- 업데이트: 2026-04-05 08:37 (KST) +- AX Agent 빈 상태 프리셋 카드의 설명 텍스트가 카드 하단에서 잘리고 있었고, Code 탭에서도 프리셋 카드 영역이 그대로 보여 빈 상태 의미가 어색했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `BuildTopicButtons()`에서 프리셋 카드, `기타` 카드, `프리셋 추가` 카드 높이를 `108`에서 `116`으로 늘리고 설명 `TextBlock.MaxHeight`도 `32`로 올려 글자 하단이 잘리지 않도록 보정했습니다. +- 같은 메서드에서 `_activeTab == "Code"`이면 `TopicButtonPanel`과 `TopicPresetScrollViewer`를 `Collapsed` 처리하고, 빈 상태 제목을 `코드 작업을 입력하세요`로 바꿔 Code 탭에서는 프리셋 기능이 보이지 않도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정 콤보를 메인 설정 스타일로 통일) +- 업데이트: 2026-04-05 08:40 (KST) +- AX Agent 내부 설정 오버레이의 콤보박스는 이전까지 기본 WPF 드롭다운에 가까운 모양이라, 메인 설정창의 커스텀 콤보와 시각 언어가 달랐습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `OverlayComboBoxToggle`, `OverlayComboBox`, `OverlayComboBoxItem` 템플릿/스타일을 추가해, 메인 [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 의 `ModernComboBox` 패턴과 유사한 토글 버튼형 헤더, 슬라이드 드롭다운, 항목 호버/선택 스타일을 적용했습니다. +- 오버레이의 `서비스`, `모델`, `Fast`, `의사결정 수준`, `실행 전 계획`, `권한 모드`, `기본 출력 형식`, `테마 스타일`, `운영 모드`, `폴더 데이터 활용` 콤보가 모두 같은 커스텀 스타일로 표시되도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정 도움말 툴팁 복구) +- 업데이트: 2026-04-05 08:43 (KST) +- AX Agent 내부 설정 오버레이는 메인 설정창과 달리 `?` 도움말 배지가 대부분 빠져 있어, 항목 이름만 보고 동작 의미를 추측해야 하는 상태였습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `OverlayHelpBadge` 스타일을 추가하고, `HelpTooltipStyle`을 재사용해 AX Agent 오버레이 항목 옆에 동일한 시각 언어의 `?` 배지를 붙일 수 있도록 정리했습니다. +- 기본/공통 영역의 `서비스`, `모델`, `기본 서버 주소`, `API 키`, `테마 스타일`, `테마 모드`, `문서 형태`, `디자인 스타일`, `운영 모드`, `폴더 데이터 활용`, `압축 시작 한도(%)`, `최대 컨텍스트 토큰`, `오류 재시도 횟수`, `최대 Agent Pass`에 각각 툴팁 설명을 추가했습니다. +- 고급/개발자 영역의 `자동 대화 압축`, `확장 스킬 사용`, `실행 전후 자동 확장`, `입력 보정 반영`, `권한 변경 반영`, `Cowork 결과 검토`, `Code 결과 검토`, `도구 병렬 실행`, `프로젝트 규칙 자동 반영`, `에이전트 메모리 사용`, `Plan Mode 도구`, `Worktree 도구`, `Team 도구`, `Cron 도구`에도 상세 설명 툴팁을 연결했습니다. +- 이 변경으로 AX Agent 내부 설정에서 각 항목의 역할과 사용 시점을 메인 설정창처럼 마우스 오버만으로 바로 확인할 수 있습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정 탭 구조 및 스킬/차단 패널 복구) +- 업데이트: 2026-04-05 08:51 (KST) +- AX Agent 내부 설정 오버레이는 중간 정리 과정에서 탭이 `공통/채팅/코워크/코드/개발자/도구/스킬/차단` 단순 구조로 축소됐고, 예전 버전에 있던 `코워크/코드` 공통 탭과 `스킬/차단` 실내용 패널이 빠진 상태였습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 좌측 네비를 `공통 / 채팅 / 코워크/코드 / 코워크 / 코드 / 개발자 / 도구 / 스킬/차단` 구조로 다시 확장하고, `스킬/차단` 탭 하단에 `차단 경로 패턴`, `차단 확장자`, `스킬 설정`, `로드된 스킬`, `폴백 모델`, `MCP 서버`, `등록된 도구/커넥터` 패널을 추가했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `RefreshOverlayEtcPanels()`, `BuildOverlayBlockedItems()`, `BuildOverlaySkillListPanel()`, `BuildOverlayFallbackModelsPanel()`, `BuildOverlayMcpServerCards()`, `BuildOverlayToolRegistryPanel()` 를 추가해, 오버레이 안에서 설정값과 런타임 상태가 바로 보이도록 복구했습니다. +- 같은 코드에서 `shared` 섹션 분기를 추가해 `코워크/코드` 공통 탭이 `Fast`, `의사결정 수준`, `실행 전 계획`, `권한 모드`, `폴더 데이터 활용`, `최대 Agent Pass` 같은 공통 항목을 함께 보여주도록 조정했습니다. +- `스킬/차단` 탭의 `슬래시 팝업 표시 개수`, `드래그 앤 드롭 AI 액션`, `드래그 앤 드롭 즉시 전송`도 오버레이 전용 컨트롤로 다시 노출하고 기존 설정 저장 경로와 연결했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 창 상단 최소화/최대화/닫기 버튼 시인성 개선) +- 업데이트: 2026-04-05 08:54 (KST) +- AX Agent [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 상단 우측 창 버튼은 일반 `GhostBtn`을 그대로 사용하고 있어, 사용자 가이드 창보다 버튼 간격이 좁고 아이콘이 작으며 호버 피드백이 약했습니다. +- 같은 파일에 `TitleBarActionButton`, `TitleBarCloseButton` 스타일을 추가해 버튼 크기를 `40x40`으로 확장하고, 버튼 사이 여백을 늘리고, 마우스 오버 시 살짝 확대되는 스케일 애니메이션과 호버 배경이 보이도록 템플릿을 분리했습니다. +- 닫기 버튼은 별도 `TitleBarCloseButton` 스타일을 적용해 호버 시 붉은 배경이 들어오도록 했고, 최소화/최대화 버튼도 아이콘 크기를 키워 창 제어 버튼이 더 명확하게 보이도록 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 세 탭 채팅 폭 공통화) +- 업데이트: 2026-04-05 09:02 (KST) +- AX Agent의 `Chat / Cowork / Code` 탭은 하단 입력 바 래퍼가 `Center` 정렬과 내용 기반 측정에 묶여 있어, 탭마다 본문 폭과 composer 폭이 다르게 보이고 전체 채팅창이 작게 느껴지는 문제가 있었습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 빈 상태 영역과 하단 입력 바 래퍼에 공통 `Stretch + MaxWidth 1280` 기준을 적용하고, 컴포저 래퍼에 `ComposerShell` 이름을 부여해 세 탭이 같은 가로 폭 축을 사용하도록 조정했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `GetMessageMaxWidth()`는 `MessageScroll.ActualWidth` 단독 비율 계산 대신 `ComposerShell.ActualWidth`를 우선 기준으로 사용하도록 바꿔, 사용자/어시스턴트 버블과 스트리밍 컨테이너가 탭과 빈 상태 여부에 상관없이 동일한 본문 폭 안에서 렌더되게 했습니다. +- 이 변경으로 Chat/Cowork/Code 전환 시 입력창과 메시지 본문이 같은 폭으로 보이고, 기존보다 더 넓은 레이아웃으로 유지됩니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 시작 직후 Style 예외 수정) +- 업데이트: 2026-04-05 09:06 (KST) +- AX Agent 창은 시작 직후 `System.Windows.FrameworkElement.Style` 설정 단계에서 `MS.Internal.NamedObject`를 `Style`로 캐스팅하지 못하는 `XamlParseException`으로 바로 종료되고 있었습니다. +- 원인은 [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 `OverlayComboBox` 스타일 내부에서 `ItemContainerStyle`이 뒤에서 선언되는 `OverlayComboBoxItem`을 `StaticResource`로 먼저 참조하던 forward reference였습니다. +- `ItemContainerStyle` 참조를 `DynamicResource OverlayComboBoxItem`으로 바꿔 런타임 리소스 해석 시점에 올바른 스타일이 연결되도록 수정했고, 같은 유형의 선참조 패턴이 더 없는 것도 추가로 확인했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (build.bat 실행 중 AX Copilot 프로세스 종료 확인 강화) +- 업데이트: 2026-04-05 09:13 (KST) +- 기존 [build.bat](/E:/AX%20Copilot%20-%20Codex/build.bat) 은 실행 중인 `AxCopilot.exe`를 `taskkill /IM ... /F` 한 번만 호출하고 2초 뒤 바로 publish 단계로 넘어가고 있어, 프로세스가 완전히 내려가지 않았는데도 빌드가 이어지거나, 권한 문제로 종료 실패한 상태가 모호하게 보이는 문제가 있었습니다. +- 프로세스 정리 루틴을 `:stop_process`로 교체하고, `CloseMainWindow()` 정상 종료 시도 후 `taskkill /IM .exe /T /F` 로 자식 프로세스까지 종료한 다음 `Get-Process`로 실제 종료 여부를 다시 확인하도록 변경했습니다. +- 프로세스가 여전히 살아 있으면 빌드를 즉시 중단하고 `Access may be denied or the app may be running with higher privileges.` 메시지를 출력하도록 해, AX Copilot이 관리자 권한 등 더 높은 권한으로 실행 중일 때 원인을 바로 알 수 있게 했습니다. +- 이 수정으로 build 배치는 실행 중 앱을 못 내렸을 때 어설프게 계속 진행하지 않고, 명확한 실패 상태로 멈춥니다. +- 검증: `cmd /c build.bat` 실행 시, 권한 문제로 살아 있는 `AxCopilot.exe`를 감지하고 즉시 실패 처리하는 동작 확인 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정의 도구/스킬 탭 확장 및 접기 구조 적용) +- 업데이트: 2026-04-05 09:27 (KST) +- AX Agent 내부 설정의 `도구`, `스킬/차단` 탭은 목록이 길게 늘어져 한 번에 보기 어렵고, 메인 설정에만 남아 있던 훅/슬래시 세부값이 따로 분리되어 있었습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `도구` 탭용 `TxtOverlayToolHookTimeoutMs`, `OverlayHookListPanel`, `훅 추가` 버튼과 `스킬/차단` 탭용 스킬 폴더 선택/열기 버튼, `TxtOverlayMaxFavoriteSlashCommands`, `TxtOverlayMaxRecentSlashCommands`를 추가해 AX Agent 내부 설정만으로 주요 확장값을 관리할 수 있게 했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서는 `BuildOverlayHookCards()`, `ShowOverlayHookEditDialog(...)`, `CreateOverlayCollapsibleSection(...)` 를 추가하고, 훅 편집/추가/삭제/활성 토글, 스킬 폴더 선택, 슬래시 핀/최근 개수 저장, 훅 타임아웃 저장을 모두 AX Agent 오버레이 저장 흐름에 연결했습니다. +- 스킬 목록은 `/스킬명` 표기로 바꾸고, 한국어 설명은 그대로 유지한 채 `직접 호출 스킬 / 자동·조건부 스킬 / 현재 사용 불가` 섹션으로 접기/펼치기 렌더링하도록 재구성했습니다. +- 도구 목록도 기존 단순 나열 대신 `파일/검색`, `코드 분석`, `문서 생성`, `에이전트` 같은 카테고리별 접기/펼치기 카드로 바꿔 긴 목록을 더 짧게 탐색할 수 있게 했습니다. +- 같은 작업에서 메인 설정 AX Agent 영역에 남아 있던 `PDF 내보내기 기본 경로`, `이미지 입력 활성화`, `코드 리뷰 도구 활성화`도 AX Agent 내부 설정의 `채팅`/`코드` 탭으로 옮겨 내부 설정 집중도를 높였습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정 개발자 탭 잔여 항목 이식) +- 업데이트: 2026-04-05 09:38 (KST) +- 메인 설정의 AX Agent 영역에는 아직 `호출 간 딜레이`, `실행 이력 상세도`, `계획 diff 심각도`, `워크플로우 시각화`, `감사 로그`, `서브에이전트 최대 수`처럼 내부 AX Agent 오버레이에 없는 개발자/운영 항목이 남아 있었습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 `OverlayDeveloperRuntimePanel`, `OverlayDeveloperExtraPanel` 을 추가하고, `호출 간 딜레이(초)`, `서브에이전트 최대 수`, `실행 이력 상세도`, `계획 diff 심각도(개수/비율)`, `워크플로우 시각화`, `전체 호출·토큰 합계 표시`, `감사 로그`, `감사 로그 폴더 열기` 행을 AX Agent 내부 설정의 `개발자` 탭 안으로 재배치했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서는 위 컨트롤들의 로드/저장 동기화, 숫자 입력 범위 검증, `실행 이력 상세도` 콤보 저장, `감사 로그 폴더 열기` 핸들러를 추가하고 `SetOverlaySection("dev")` 분기에서 해당 패널이 `개발자` 탭에서만 보이도록 연결했습니다. +- 이 변경으로 AX Agent 내부 설정은 기존 `공통 / 채팅 / 코워크/코드 / 코워크 / 코드 / 개발자 / 도구 / 스킬/차단` 탭 구조를 유지한 채, 메인 설정에 남아 있던 AX Agent 전용 세부값 대부분을 각 기능 탭에 더 가깝게 재배치한 상태가 됐습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 메인 설정 잔존 제거 및 내부 설정 재배치) +- 업데이트: 2026-04-05 09:54 (KST) +- 일반 설정창에서 `AX Agent` 탭이 계속 보이던 이유는 [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 의 `ApplyAiEnabledState()`가 `AiEnabled=true` 상태일 때 `AgentTabItem.Visibility`를 다시 `Visible`로 되돌리고 있었기 때문이었습니다. +- 같은 메서드에서 `AgentTabItem.Visibility`를 항상 `Collapsed`로 유지하도록 바꿔, 내부 설정으로 이관 중인 AX Agent 전용 설정이 메인 설정에 다시 나타나지 않게 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서는 `ApplyOverlaySettingsChanges()`와 `ChkOverlayAiEnabled_Changed()` 경로에서 `AiEnabled`를 더 이상 토글하지 않고 항상 `true`로 유지하도록 바꿨고, `OverlayAiEnabledRow`도 내부 설정에서 숨겨 AX Agent 사용 토글을 제거했습니다. +- 내부 AX Agent 설정 탭 배치도 다시 조정해 `서비스/모델`과 `운영 모드`를 `공통` 탭으로 옮기고, `문서 형태`와 `디자인 스타일`은 채팅 탭이 아니라 `코워크` 탭에만 보이도록 정리했습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 `압축 시작 한도(%)`, `최대 컨텍스트 토큰` 행은 숫자 입력 칸 대신 `60/70/80/90%`, `4K/16K/64K/256K/1M` 프리셋 카드로 바꾸고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 선택 반영/하이라이트 동기화 로직(`OverlayCompactPresetCard_MouseLeftButtonUp`, `OverlayContextPresetCard_MouseLeftButtonUp`, `RefreshOverlayTokenPresetCards`)을 추가해 내부 설정 저장과 즉시 반영을 통합했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (메인 설정 AX Agent의 표현 수준 행 숨김) +- 업데이트: 2026-04-05 09:56 (KST) +- 일반 설정의 AX Agent 탭 화면에는 `표현 수준` 행이 여전히 남아 있어, 내부 고정 정책과 달리 사용자가 선택 가능한 옵션처럼 보이는 상태였습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 의 `표현 수준` row `Border`에 `Visibility="Collapsed"`를 적용해, 메인 설정 화면에서는 해당 항목이 더 이상 보이지 않게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 컴포저 과확장 레이아웃 보정) +- 업데이트: 2026-04-05 09:58 (KST) +- AX Agent 채팅 화면의 입력 컴포저는 [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 `HorizontalAlignment="Stretch"`와 넓은 최대폭을 사용하고 있었고, [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `ApplyExpressionLevelUi()`가 `rich` 모드에서 `InputBox.MaxHeight = 220`까지 다시 늘려 입력창이 과하게 커지는 문제가 있었습니다. +- `ComposerShell`을 `Center + Width/MaxWidth 640`으로 바꿔 채팅창이 전체 가로폭을 계속 채우지 않도록 했고, 입력창 최대 높이도 `rich 120 / balanced 108 / simple 96`으로 낮춰 여러 줄 입력 시에도 적정 높이 범위 안에서만 늘어나도록 조정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (Gemini Chat 빈 응답/멈춤 우회) +- 업데이트: 2026-04-05 10:02 (KST) +- AX Agent Chat 탭에서 Gemini를 선택했을 때, API 키를 넣은 뒤에도 임시 스트리밍 컨테이너만 뜨고 응답이 비거나 진행 상태가 오래 남는 문제가 있었습니다. 현재 구현상 원인은 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 일반 Chat 스트리밍 경로가 Gemini SSE 응답과 맞물릴 때 완료 신호를 안정적으로 처리하지 못하던 쪽에 가까웠습니다. +- 일반 전송 흐름과 `다시 생성` 흐름 둘 다에서 현재 서비스가 `gemini`일 경우 `_llm.StreamAsync(...)` 대신 `_llm.SendAsync(...)` 비스트리밍 경로를 사용하도록 변경해, Chat 탭에서는 Gemini 응답을 한 번에 받아 안정적으로 렌더하도록 우회했습니다. +- 같은 파일의 `GetMessageMaxWidth()`는 `320~720` 범위로 재조정해 메시지 폭이 컴포저보다 더 커지지 않게 했고, `FinalizeStreamingContainer(...)`에서는 응답 내용이 비어 있을 경우 `(빈 응답)`으로 치환해 완전히 빈 말풍선이 남지 않게 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 채팅 컴포저 높이 재고정 및 빈 assistant 말풍선 제거) +- 업데이트: 2026-04-05 10:10 (KST) +- AX Agent Chat 탭에서 입력창 텍스트가 거의 없는데도 컴포저 높이가 계속 커지고, 응답이 비정상 종료된 뒤 내용 없는 assistant 카드가 다시 렌더되는 문제가 있었습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `UpdateInputBoxHeight()`를 추가해 입력창 높이를 실제 줄 수 기준으로 직접 계산하고 `MinHeight~MaxHeight` 범위 안에서만 유지하도록 변경했습니다. 이때 최대 높이를 넘길 경우에만 내부 세로 스크롤을 표시하도록 바꿔, 내용이 없는데도 큰 빈 입력 영역이 남지 않게 했습니다. +- 같은 파일의 `RenderMessages()`는 비어 있는 assistant 메시지를 렌더 대상에서 제외하도록 필터를 추가했고, 일반 전송/재생성 흐름에서도 완료 직전 `assistantMsg.Content`가 비어 있으면 `(빈 응답)`으로 확정 저장하도록 정리해 재오픈 시에도 빈 말풍선이 다시 나타나지 않게 보정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 채팅 탭의 중복 테마 설정 제거) +- 업데이트: 2026-04-05 10:12 (KST) +- AX Agent 내부 설정의 `채팅` 탭에는 채팅과 직접 관련 없는 `테마 스타일`, `테마 모드` 블록이 남아 있어, 기능 분류 기준과 맞지 않는 상태였습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 해당 두 블록을 삭제해 채팅 탭에는 `PDF 내보내기 기본 경로`, `이미지 입력 활성화` 같은 실제 채팅 관련 설정만 남도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정에 등록 모델 관리 이식) +- 업데이트: 2026-04-05 10:16 (KST) +- AX Agent 내부 설정의 `공통` 탭에는 서비스/모델 선택만 있고, 메인 설정에 있던 `등록 모델 추가/편집/삭제` 기능이 빠져 있어 사내 모델 관리가 반쪽 상태였습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 `OverlayModelEditorPanel` 아래에 `등록 모델 관리` 헤더, `모델 추가` 버튼, `OverlayRegisteredModelsPanel`을 추가해 내부 설정 안에서도 사내 모델 관리 UI가 보이도록 확장했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서는 `BuildOverlayRegisteredModelsPanel()`을 추가하고, 메인 설정에서 쓰던 [ModelRegistrationDialog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ModelRegistrationDialog.cs) 를 그대로 호출해 `Ollama/vLLM` 등록 모델의 `추가/편집/삭제/선택`을 내부 설정에서 직접 수행하도록 연결했습니다. +- 저장 경로는 기존 `Llm.RegisteredModels`를 그대로 사용해, 메인 설정과 내부 설정이 같은 모델 목록을 보도록 맞췄고, `PersistOverlaySettingsState()` 시점에 동일하게 저장/갱신되도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정의 압축 시작 한도 분류 보정) +- 업데이트: 2026-04-05 10:19 (KST) +- `압축 시작 한도(%)` 행은 성격상 `코워크/코드 공통` 설정인데, 기존 `SetOverlaySection()` 분기에서는 여러 탭에서 함께 열리도록 되어 있어 분류가 어색했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `OverlayAnchorAdvanced.Visibility` 조건을 `showShared` 전용으로 좁혀, 해당 항목이 `코워크/코드 공통` 탭에서만 보이도록 보정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정 탭 헤더를 본문 최상단으로 정리) +- 업데이트: 2026-04-05 10:27 (KST) +- AX Agent 내부 설정 화면은 탭별 제목/설명이 `서비스와 모델` 블록 아래쪽에 위치해 있어, 사용자가 현재 탭의 범위를 바로 파악하기 어려운 상태였습니다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에 상단 전용 헤더(`OverlaySectionHeading`, `OverlayTopHeadingTitle`, `OverlayTopHeadingDescription`)를 추가하고, `OverlaySectionDetail` 안의 중복 헤더는 숨겨 탭 제목과 설명이 본문 맨 위에 먼저 보이도록 재배치했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `SetOverlaySection()`도 새 상단 헤더에 맞춰 각 탭별 제목/설명을 채우도록 조정했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (Chat 탭 입력창 누적 확장 및 assistant 메시지 미표시 보정) +- 업데이트: 2026-04-05 10:23 (KST) +- Chat 탭에서는 엔터를 칠 때마다 컴포저 높이가 누적 확장되고, 하단 토큰은 증가하지만 assistant 메시지가 화면에 남지 않는 문제가 있었습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `UpdateInputBoxHeight()`를 재조정해 Chat 탭은 항상 고정 높이 입력창을 사용하고, Cowork/Code 탭만 명시적 줄 수 기준으로 멀티라인 높이 계산을 하도록 분리했습니다. +- 같은 파일에 `SyncLatestAssistantMessage(ChatConversation conv, string content)`를 추가해 응답 완료 시 최신 assistant 메시지 내용을 대화 객체에 확정 반영하고, 이후 `RenderMessages(preserveViewport: true)`를 한 번 더 호출해 스트리밍/비스트리밍 완료 뒤에도 실제 저장 대화 기준으로 메시지가 다시 보이도록 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (메인 설정의 AX Agent 진입을 내부 설정 바로가기 중심으로 정리) +- 업데이트: 2026-04-05 10:35 (KST) +- 사용자 요청 기준으로 AX Agent 관련 설정은 메인 설정 탭에서 직접 다루지 않고, AX Agent 내부 설정으로 모으는 방향으로 다시 정리했습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 의 `일반` 탭 하단에 `AX Agent 설정 바로가기` 카드를 추가해, 일반 설정 화면에서도 AX Agent 채팅창을 열고 내부 설정 오버레이를 곧바로 표시할 수 있도록 했습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 의 `ApplyMainTabVisibility()`에서는 메인 설정의 표시 대상에서 `AX Agent`를 완전히 제외했고, `SelectAgentSettingsTab()` 및 `BtnAgentShortcut_Click()`은 더 이상 숨겨진 AX Agent 탭으로 이동하지 않고 [App.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/App.xaml.cs) 의 `OpenAgentSettingsInChat()`을 호출하도록 전환했습니다. +- 이 변경으로 메인 설정에 남아 있던 AX Agent 진입 버튼도 모두 같은 내부 설정 경로를 사용하게 되어, 설정 위치가 둘로 나뉘어 보이던 흐름을 줄였습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (Chat 탭도 줄바꿈 기준으로 입력창 높이 확장) +- 업데이트: 2026-04-05 10:41 (KST) +- 직전 보정에서 Chat 탭 입력창을 고정 높이로 묶었는데, 사용자 기준으로는 Chat도 `Shift+Enter` 줄바꿈이 있을 때는 자연스럽게 높이가 늘어나야 했고, 고정 분기 때문에 의도와 다른 동작이 되고 있었습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `UpdateInputBoxHeight()`에서 `Chat` 탭 예외 분기를 제거해, 이제 세 탭 모두 실제 개행 문자 수를 기준으로 높이를 계산합니다. +- 결과적으로 `Enter` 전송만으로는 높이가 늘지 않고, `Shift+Enter`로 줄이 추가된 경우에만 높이가 증가하며, 최대 높이를 넘길 때만 내부 스크롤이 나타나도록 다시 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (메인 설정의 숨김 AX Agent 옛 UI 잔재 1차 제거) +- 업데이트: 2026-04-05 10:48 (KST) +- 메인 설정 XAML에는 더 이상 사용자에게 보이지 않는데도 남아 있던 AX Agent 관련 숨김 블록과 중복 진입 버튼이 있어, 내부 설정 기준으로 통합된 현재 UX와 맞지 않는 상태였습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 에서 `일반` 탭의 숨김 `AI 기능`/`운영 모드` 행을 제거했고, 하단 전역 버튼 바에 있던 중복 `AX Agent 설정` 버튼도 삭제했습니다. 이제 메인 설정에서 AX Agent로 들어가는 진입은 일반 탭 본문의 전용 바로가기 카드로만 남습니다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 의 `ApplyAiEnabledState()`와 `ApplyOperationModeState()`도 숨김 일반 탭 컨트롤을 더 이상 참조하지 않도록 정리해, 보이지 않는 UI를 전제로 둔 동기화 코드가 돌지 않게 했습니다. +- 이번 단계는 1차 정리이며, 아직 XAML 하단에 남아 있는 구형 `AgentTabItem` 본문 전체 제거는 내부 참조가 많아 다음 단계에서 분리 정리할 예정입니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 채팅/코워크/코드 응답 표시 안정화) +- 업데이트: 2026-04-05 11:02 (KST) +- 사용자 보고 기준으로 AX Agent는 전송할 때마다 입력창 높이가 비정상적으로 남고, Chat 탭은 토큰만 올라가는데 응답 본문이 안 보이며, Cowork/Code는 실행 로그 문구 잔상만 남는 상태였습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에서 `QueueComposerDraft()`, `BtnDraftClear_Click()`, `StartNextQueuedDraftIfAny()`, `SendMessageAsync()`에서 입력 텍스트를 비우거나 다시 채운 직후 `UpdateInputBoxHeight()`를 명시적으로 호출하도록 바꿔, 입력창 높이가 이전 값으로 남는 현상을 줄였습니다. +- `SendMessageAsync()`의 일반 Chat 흐름은 스트리밍 대신 비스트리밍 응답을 우선 사용하게 조정해, Chat 탭에서 토큰 사용량은 집계되는데 assistant 메시지가 비거나 늦게 반영되던 경로를 안정화했습니다. +- 같은 메서드에 `BuildAssistantFallbackContent()`를 추가해 Cowork/Code에서 최종 assistant 텍스트가 비어 있으면 최근 실행 이벤트 요약을 fallback 응답으로 남기게 했고, 응답 완료 후 `conv.ShowExecutionHistory = false`를 적용해 실행 로그 카드 잔상이 기본 화면을 덮지 않도록 정리했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 + +### 2026-04-05 추가 진행 기록 (AX Agent 내부 설정 도구/스킬 섹션 기본 접힘 처리) +- 업데이트: 2026-04-05 11:08 (KST) +- 사용자 요청 기준으로 AX Agent 내부 설정의 `도구`, `스킬/차단` 탭은 긴 목록이 처음부터 모두 펼쳐져 있기보다, 필요할 때만 열어보는 구조가 더 적합했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `AddOverlaySkillSection()`, `BuildOverlayToolRegistryPanel()`, `BuildOverlayHookCards()`에서 `CreateOverlayCollapsibleSection(...)` 기본 확장값을 모두 `false`로 바꿔, 스킬 목록/도구 카테고리/훅 목록이 기본적으로 닫힌 상태로 열리게 했습니다. +- 이 변경으로 AX Agent 내부 설정 진입 시 `스킬/차단`, `도구` 탭이 더 압축된 상태로 보이고, 필요한 항목만 선택적으로 펼쳐 확인할 수 있습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 +- Document update: 2026-04-05 11:22 (KST) - Started stabilizing AX Agent chat around a more `claw-code`-like single-source render path. The composer shell is now bottom-aligned instead of stretching vertically, direct user-bubble injection during send was removed in favor of model-backed `RenderMessages()` refresh, and live execution banners now inject into the visible chat only when execution-history display is explicitly enabled. +- Document update: 2026-04-05 11:22 (KST) - For Cowork/Code runs, `ShowExecutionHistory` is now forced off at run start so intermediate rerenders no longer rehydrate transient execution banners into the conversation body. +- Document update: 2026-04-05 11:22 (KST) - Chat tab now skips creation of the temporary streaming container altogether. Since Chat already routes through non-streaming `SendAsync()`, the final assistant text is now committed directly to the conversation model and rendered from there instead of relying on a transient placeholder card. +- Document update: 2026-04-05 11:22 (KST) - Cowork/Code now also skip the temporary streaming container path. Those tabs receive final agent-loop results rather than incremental chunks, so the UI now avoids showing a transient empty assistant card before the final rerender. +- Document update: 2026-04-05 11:22 (KST) - Main settings tab visibility now permanently hides the legacy `AX Agent` tab regardless of AI enablement, so the remaining supported entry point is the AX Agent in-chat internal settings overlay plus the general-settings shortcut. +- Document update: 2026-04-05 11:29 (KST) - Removed the early assistant-placeholder insertion from `SendMessageAsync()`. Assistant messages are now appended only after the final response text is known, reducing the empty-bubble / token-only failure mode in AX Agent chat. +- Document update: 2026-04-05 11:29 (KST) - Added a `MainSettingsTab` selection redirect so any attempt to land on the legacy hidden AX Agent tab immediately routes to the AX Agent in-chat settings shortcut instead. +- Document update: 2026-04-05 11:34 (KST) - Added `Temperature` to the AX Agent in-chat settings overlay and wired it into the same deferred-save path used by the other numeric overlay settings. The value is normalized to the 0.0–2.0 range and persisted from the overlay itself. +- Document update: 2026-04-05 11:42 (KST) - Added `Services/Agent/AxAgentExecutionEngine.cs` as an AX-side execution-prep engine modeled after the `claw-code` split between input processing and session execution. `ChatWindow` now routes outbound message preparation and final assistant-message commit through that engine instead of assembling and appending everything inline. +- Document update: 2026-04-05 11:11 (KST) - Collapsed the duplicated Cowork/Code agent-loop branches inside `ChatWindow.SendMessageAsync()` into `RunAgentLoopAsync(...)`. Both tabs now share the same execution wrapper for workflow-analyzer open, cumulative-token reset, event hookup, completion notification, and cleanup, which moves AX Agent closer to a single execution path modeled after the `claw-code` engine split. +- Document update: 2026-04-05 11:11 (KST) - Reworked AX Agent composer resizing to use `TextBox.MinLines/MaxLines` instead of manually pinning `Height`, so the composer grows only with actual line breaks and is less likely to remain oversized after send. +- Document update: 2026-04-05 11:11 (KST) - Replaced the in-overlay text-entry controls for `Temperature`, `MaxRetryOnError`, `MaxAgentIterations`, `FreeTierDelaySeconds`, and `MaxSubAgents` with slider-based controls plus current-value badges, following the older settings-window interaction pattern and reducing mis-entry risk. +- Document update: 2026-04-05 11:11 (KST) - Moved `Temperature` and `MaxAgentIterations` visibility to the developer tab only inside the AX Agent overlay, tightening the common tabs around user-facing settings and pushing expert/debug knobs toward the developer section. +- Document update: 2026-04-05 11:11 (KST) - Removed the general-tab bottom shortcut card for AX Agent settings and added a dedicated `AX Agent` entry to the left settings navigation. Selecting that nav entry now routes straight into the AX Agent in-chat internal settings overlay, while the legacy hidden `AgentTabItem` remains suppressed. +- Document update: 2026-04-05 11:22 (KST) - Continued migrating the remaining AX Agent in-chat numeric controls away from freeform text entry. `ToolHookTimeoutMs`, `SlashPopupPageSize`, `MaxFavoriteSlashCommands`, and `MaxRecentSlashCommands` now use slider-based controls with live value badges in the internal overlay, matching the safer interaction style from the old settings window. +- Document update: 2026-04-05 11:22 (KST) - Synced the new slider-backed skill/tool values through both `RefreshOverlayVisualState(...)` and `RefreshOverlayEtcPanels(...)`, so section changes and overlay reopen now restore the same values across the hidden compatibility textboxes, sliders, and badges. +- Document update: 2026-04-05 11:24 (KST) - Removed the legacy hidden `AgentTabItem` from `MainSettingsTab.Items` at settings-window construction time, so the old AX Agent settings body can no longer be selected through tab restoration or visibility changes. The left navigation `AX Agent` shortcut remains the only supported entry and now routes directly into the in-chat AX Agent settings overlay. +- Document update: 2026-04-05 11:25 (KST) - Added a `HasLegacyAgentTab()` guard around the remaining legacy AX Agent initialization path in `SettingsWindow`. Once the old tab is removed from `MainSettingsTab.Items`, calls such as `MoveBlockSectionToEtc()`, `BuildServiceModelPanels()`, `BuildToolRegistryPanel()`, `LoadAdvancedSettings()`, `SyncAgentSelectionCards()`, and `ApplyAgentSubTabVisibility()` now stop running, which reduces hidden coupling to the retired settings body while the XAML cleanup continues. +- Document update: 2026-04-05 11:27 (KST) - Restored per-tool enable/disable control inside the AX Agent in-chat `도구` tab. `BuildOverlayToolRegistryPanel()` now renders each tool row with a `ToggleSwitch`, persists changes through `Llm.DisabledTools`, and refreshes the overlay immediately so the tool availability state is managed from the AX Agent internal settings instead of only the legacy settings window. +- Document update: 2026-04-05 11:28 (KST) - Updated [AGENTS.md](/E:/AX%20Copilot%20-%20Codex/AGENTS.md) to explicitly require unified control patterns for settings input: all on/off fields must use `ToggleSwitch`, numeric fields should default to the established slider + value-badge pattern, and equivalent settings in main settings vs. AX Agent internal settings should not diverge in input style. +- Document update: 2026-04-05 11:31 (KST) - Fixed the startup-time `NullReferenceException` that appeared during AX Agent prewarm. `ChatWindow` now resolves overlay LLM settings through `TryGetOverlayLlmSettings()` and all overlay slider `ValueChanged` handlers short-circuit when the settings graph is not ready yet, preventing premature writes during XAML initialization. + +### 2026-04-05 추가 진행 기록 (Agent Compare 기준 런처 기능 이식 1차) +- 업데이트: 2026-04-05 11:58 (KST) +- 사용자 요청 기준을 `claw-code`가 아니라 우선 `Agent Compare`의 런처 기능 이식으로 다시 고정하고, 현재 런처와 비교해 실제로 빠져 있던 하단 위젯 바 외 기능을 점검했습니다. +- [LauncherViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/LauncherViewModel.cs)에 검색 히스토리 탐색 상태(`_isHistoryNavigation`, `_historyIndex`)를 복구하고, `SelectedItem` 변경 시 선택 항목 미리보기 업데이트가 바로 돌도록 다시 연결했습니다. `OnShown()`은 빠른 실행 칩을 로드하고, 실행 시점에는 검색어를 [SearchHistoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/SearchHistoryService.cs)에 저장하도록 정리했습니다. +- 새로 추가한 [LauncherViewModel.LauncherExtras.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/LauncherViewModel.LauncherExtras.cs)는 `Agent Compare`에 있던 `빠른 실행 칩`, `검색 히스토리 이동`, `선택 항목 미리보기 패널` 보조 로직을 현재 런처 구조에 맞게 분리한 partial입니다. 텍스트/이미지/PDF/미디어 파일에 대한 간단한 메타 미리보기를 제공하고, 실행 상위 경로를 칩으로 다시 띄웁니다. +- [LauncherWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.xaml)에는 입력창 아래 빠른 실행 칩 영역과 결과 리스트 아래 미리보기 패널을 추가했고, 기존에 먼저 이식한 위젯 바와 겹치지 않도록 행 구조를 `8개 Row` 기준으로 재배치했습니다. 토스트 오버레이의 `Grid.RowSpan`도 새 레이아웃에 맞춰 `4`로 확장했습니다. +- [LauncherWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.xaml.cs)에는 `F3` 빠른 미리보기, `F4` 화면 OCR 단축키를 다시 연결했고, 창이 숨겨질 때 QuickLook 창도 같이 닫히도록 정리했습니다. +- [LauncherWindow.Shell.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.Shell.cs), [QuickLookWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/QuickLookWindow.xaml), [QuickLookWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/QuickLookWindow.xaml.cs), [QuickActionChip.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/QuickActionChip.cs), [SearchHistoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/SearchHistoryService.cs)을 추가해 `Agent Compare` 쪽 런처 보조 기능이 현재 코드베이스에서 독립적으로 유지되도록 분리했습니다. +- [UsageRankingService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/UsageRankingService.cs)에는 `GetTopItems(int)`를 추가해, 빠른 실행 칩이 최근 실행 상위 경로를 직접 읽을 수 있도록 했습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`` 경고 0 / 오류 0 diff --git a/src/AxCopilot/Models/QuickActionChip.cs b/src/AxCopilot/Models/QuickActionChip.cs new file mode 100644 index 0000000..4bfcde3 --- /dev/null +++ b/src/AxCopilot/Models/QuickActionChip.cs @@ -0,0 +1,14 @@ +using System.Windows.Media; + +namespace AxCopilot.Models; + +/// +/// 런처 입력창 아래에 표시되는 빠른 실행 칩 모델입니다. +/// 최근 자주 실행한 경로를 한 번 더 검색하지 않고 바로 열 수 있습니다. +/// +public record QuickActionChip( + string Title, + string Symbol, + string Path, + Brush Background +); diff --git a/src/AxCopilot/Services/PerformanceMonitorService.cs b/src/AxCopilot/Services/PerformanceMonitorService.cs new file mode 100644 index 0000000..7aedcd2 --- /dev/null +++ b/src/AxCopilot/Services/PerformanceMonitorService.cs @@ -0,0 +1,133 @@ +using System.IO; +using System.Runtime.InteropServices; + +namespace AxCopilot.Services; + +internal sealed class PerformanceMonitorService +{ + public static readonly PerformanceMonitorService Instance = new(); + + [StructLayout(LayoutKind.Sequential)] + private struct FILETIME + { + public uint Low; + public uint High; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MEMORYSTATUSEX + { + public uint dwLength; + public uint dwMemoryLoad; + public ulong ullTotalPhys; + public ulong ullAvailPhys; + public ulong ullTotalPageFile; + public ulong ullAvailPageFile; + public ulong ullTotalVirtual; + public ulong ullAvailVirtual; + public ulong ullAvailExtendedVirtual; + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool GetSystemTimes(out FILETIME idle, out FILETIME kernel, out FILETIME user); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); + + public double CpuPercent { get; private set; } + public double RamPercent { get; private set; } + public string RamText { get; private set; } = ""; + public double DiskCPercent { get; private set; } + public string DiskCText { get; private set; } = ""; + + private System.Threading.Timer? _timer; + private FILETIME _prevIdle; + private FILETIME _prevKernel; + private FILETIME _prevUser; + private bool _hasPrev; + + private PerformanceMonitorService() { } + + public void StartPolling() + { + if (_timer != null) + return; + + _timer = new System.Threading.Timer(_ => Sample(), null, 0, 2000); + } + + public void StopPolling() + { + _timer?.Dispose(); + _timer = null; + _hasPrev = false; + } + + private void Sample() + { + SampleCpu(); + SampleRam(); + SampleDisk(); + } + + private void SampleCpu() + { + try + { + if (!GetSystemTimes(out var idle, out var kernel, out var user)) + return; + + if (_hasPrev) + { + var idleDelta = ToUlong(idle) - ToUlong(_prevIdle); + var kernelDelta = ToUlong(kernel) - ToUlong(_prevKernel); + var userDelta = ToUlong(user) - ToUlong(_prevUser); + var total = kernelDelta + userDelta; + var busy = total - idleDelta; + CpuPercent = total > 0 ? Math.Clamp(100.0 * busy / total, 0, 100) : 0; + } + + _prevIdle = idle; + _prevKernel = kernel; + _prevUser = user; + _hasPrev = true; + } + catch { } + } + + private void SampleRam() + { + try + { + var mem = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf() }; + if (!GlobalMemoryStatusEx(ref mem)) + return; + + RamPercent = mem.dwMemoryLoad; + var usedGb = (mem.ullTotalPhys - mem.ullAvailPhys) / (1024.0 * 1024 * 1024); + var totalGb = mem.ullTotalPhys / (1024.0 * 1024 * 1024); + RamText = $"{usedGb:F1}/{totalGb:F0}GB"; + } + catch { } + } + + private void SampleDisk() + { + try + { + var drive = DriveInfo.GetDrives() + .FirstOrDefault(d => d.IsReady && d.Name.StartsWith("C", StringComparison.OrdinalIgnoreCase)); + if (drive == null) + return; + + var usedBytes = drive.TotalSize - drive.AvailableFreeSpace; + DiskCPercent = 100.0 * usedBytes / drive.TotalSize; + var usedGb = usedBytes / (1024.0 * 1024 * 1024); + var totalGb = drive.TotalSize / (1024.0 * 1024 * 1024); + DiskCText = $"C:{usedGb:F0}/{totalGb:F0}GB"; + } + catch { } + } + + private static ulong ToUlong(FILETIME ft) => ((ulong)ft.High << 32) | ft.Low; +} diff --git a/src/AxCopilot/Services/SearchHistoryService.cs b/src/AxCopilot/Services/SearchHistoryService.cs new file mode 100644 index 0000000..f04e19f --- /dev/null +++ b/src/AxCopilot/Services/SearchHistoryService.cs @@ -0,0 +1,108 @@ +using System.IO; +using System.Text.Json; + +namespace AxCopilot.Services; + +/// +/// 런처 검색 히스토리를 로컬 파일로 관리합니다. +/// 위/아래 키로 이전 검색어를 다시 불러올 때 사용됩니다. +/// +internal static class SearchHistoryService +{ + private const int MaxItems = 50; + + private static readonly string HistoryFile = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "AxCopilot", + "search_history.json"); + + private static readonly object SyncLock = new(); + private static List _history = new(); + private static bool _loaded; + + public static void Add(string query) + { + if (string.IsNullOrWhiteSpace(query)) + return; + + var trimmed = query.Trim(); + if (trimmed.Length < 2) + return; + + EnsureLoaded(); + lock (SyncLock) + { + if (_history.Count > 0 && string.Equals(_history[0], trimmed, StringComparison.Ordinal)) + return; + + _history.Remove(trimmed); + _history.Insert(0, trimmed); + + if (_history.Count > MaxItems) + _history.RemoveAt(_history.Count - 1); + } + + _ = SaveAsync(); + } + + public static IReadOnlyList GetAll() + { + EnsureLoaded(); + lock (SyncLock) + return _history.AsReadOnly(); + } + + public static void Clear() + { + lock (SyncLock) + _history.Clear(); + + _ = SaveAsync(); + } + + private static void EnsureLoaded() + { + if (_loaded) + return; + + lock (SyncLock) + { + if (_loaded) + return; + + try + { + if (File.Exists(HistoryFile)) + { + var json = File.ReadAllText(HistoryFile); + _history = JsonSerializer.Deserialize>(json) ?? new List(); + } + } + catch (Exception ex) + { + LogService.Warn($"search_history.json 로드 실패: {ex.Message}"); + _history = new List(); + } + + _loaded = true; + } + } + + private static async Task SaveAsync() + { + try + { + List snapshot; + lock (SyncLock) + snapshot = new List(_history); + + Directory.CreateDirectory(Path.GetDirectoryName(HistoryFile)!); + var json = JsonSerializer.Serialize(snapshot); + await File.WriteAllTextAsync(HistoryFile, json); + } + catch (Exception ex) + { + LogService.Warn($"search_history.json 저장 실패: {ex.Message}"); + } + } +} diff --git a/src/AxCopilot/Services/ServerStatusService.cs b/src/AxCopilot/Services/ServerStatusService.cs new file mode 100644 index 0000000..3cbde87 --- /dev/null +++ b/src/AxCopilot/Services/ServerStatusService.cs @@ -0,0 +1,115 @@ +using System.Net.Http; +using AxCopilot.Models; + +namespace AxCopilot.Services; + +internal sealed class ServerStatusService +{ + public static readonly ServerStatusService Instance = new(); + + private static readonly HttpClient Http = new() + { + Timeout = TimeSpan.FromMilliseconds(1500) + }; + + public bool OllamaOnline { get; private set; } + public bool LlmOnline { get; private set; } + public bool McpOnline { get; private set; } + public string McpName { get; private set; } = "MCP"; + + public event EventHandler? StatusChanged; + + private System.Threading.Timer? _timer; + private string _ollamaEndpoint = "http://localhost:11434"; + private string _llmEndpoint = ""; + private string _llmService = "Ollama"; + private string _mcpEndpoint = ""; + + private ServerStatusService() { } + + public void Start(AppSettings? settings = null) + { + LoadEndpoints(settings); + if (_timer != null) + return; + + _timer = new System.Threading.Timer(async _ => await CheckAllAsync(), null, 0, 15000); + } + + public void Stop() + { + _timer?.Dispose(); + _timer = null; + } + + public void Refresh(AppSettings? settings = null) + { + LoadEndpoints(settings); + _ = CheckAllAsync(); + } + + private void LoadEndpoints(AppSettings? settings) + { + var llm = settings?.Llm; + if (llm == null) + return; + + _ollamaEndpoint = llm.OllamaEndpoint?.TrimEnd('/') ?? "http://localhost:11434"; + _llmService = llm.Service ?? "Ollama"; + _llmEndpoint = string.Equals(_llmService, "vLLM", StringComparison.OrdinalIgnoreCase) + ? (llm.VllmEndpoint?.TrimEnd('/') ?? "") + : _ollamaEndpoint; + + var mcp = llm.McpServers?.FirstOrDefault(s => s.Enabled && !string.IsNullOrWhiteSpace(s.Url)); + if (mcp != null) + { + McpName = mcp.Name; + _mcpEndpoint = mcp.Url?.TrimEnd('/') ?? ""; + } + else + { + McpName = "MCP"; + _mcpEndpoint = ""; + } + } + + private async Task CheckAllAsync() + { + var ollamaTask = PingAsync(_ollamaEndpoint + "/api/version"); + var llmTask = string.IsNullOrEmpty(_llmEndpoint) || _llmEndpoint == _ollamaEndpoint + ? ollamaTask + : PingAsync(_llmEndpoint); + var mcpTask = string.IsNullOrEmpty(_mcpEndpoint) + ? Task.FromResult(false) + : PingAsync(_mcpEndpoint); + + await Task.WhenAll(ollamaTask, llmTask, mcpTask).ConfigureAwait(false); + + var changed = OllamaOnline != ollamaTask.Result || + LlmOnline != llmTask.Result || + McpOnline != mcpTask.Result; + + OllamaOnline = ollamaTask.Result; + LlmOnline = llmTask.Result; + McpOnline = mcpTask.Result; + + if (changed) + StatusChanged?.Invoke(this, EventArgs.Empty); + } + + private static async Task PingAsync(string url) + { + if (string.IsNullOrWhiteSpace(url)) + return false; + + try + { + var resp = await Http.GetAsync(url).ConfigureAwait(false); + return resp.IsSuccessStatusCode || (int)resp.StatusCode < 500; + } + catch + { + return false; + } + } +} diff --git a/src/AxCopilot/Services/UsageRankingService.cs b/src/AxCopilot/Services/UsageRankingService.cs index 4be719a..11f3886 100644 --- a/src/AxCopilot/Services/UsageRankingService.cs +++ b/src/AxCopilot/Services/UsageRankingService.cs @@ -60,6 +60,23 @@ internal static class UsageRankingService .Select(x => x.item); } + /// + /// 실행 횟수 상위 항목을 반환합니다. + /// 빠른 실행 칩처럼 경로 자체 목록이 필요한 화면에서 사용합니다. + /// + public static IReadOnlyList> GetTopItems(int maxCount) + { + EnsureLoaded(); + lock (_lock) + { + return _counts + .OrderByDescending(x => x.Value) + .ThenBy(x => x.Key, StringComparer.OrdinalIgnoreCase) + .Take(Math.Max(0, maxCount)) + .ToList(); + } + } + // ─── 내부 ────────────────────────────────────────────────────────────────── private static void EnsureLoaded() diff --git a/src/AxCopilot/Services/WeatherWidgetService.cs b/src/AxCopilot/Services/WeatherWidgetService.cs new file mode 100644 index 0000000..5dfd8fc --- /dev/null +++ b/src/AxCopilot/Services/WeatherWidgetService.cs @@ -0,0 +1,52 @@ +using System.Net.Http; + +namespace AxCopilot.Services; + +internal static class WeatherWidgetService +{ + private static string? _cached; + private static DateTime _cacheTime = DateTime.MinValue; + private static bool _fetching; + private static readonly TimeSpan Ttl = TimeSpan.FromMinutes(30); + + public static string CachedText => _cached ?? "--"; + + public static async Task RefreshAsync(bool internalMode) + { + if (internalMode) + { + _cached = "--"; + return; + } + + if (_cached != null && DateTime.Now - _cacheTime < Ttl) + return; + if (_fetching) + return; + + _fetching = true; + try + { + using var client = new HttpClient(); + client.Timeout = TimeSpan.FromSeconds(6); + var raw = await client.GetStringAsync("https://wttr.in/?format=%c+%t"); + _cached = raw.Trim().Replace("+", " "); + _cacheTime = DateTime.Now; + } + catch (Exception ex) + { + LogService.Warn($"날씨 위젯 갱신 실패: {ex.Message}"); + _cached ??= "--"; + } + finally + { + _fetching = false; + } + } + + public static void Invalidate() + { + _cached = null; + _cacheTime = DateTime.MinValue; + } +} diff --git a/src/AxCopilot/ViewModels/LauncherViewModel.LauncherExtras.cs b/src/AxCopilot/ViewModels/LauncherViewModel.LauncherExtras.cs new file mode 100644 index 0000000..f47caee --- /dev/null +++ b/src/AxCopilot/ViewModels/LauncherViewModel.LauncherExtras.cs @@ -0,0 +1,322 @@ +using System.Collections.ObjectModel; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using AxCopilot.Models; +using AxCopilot.SDK; +using AxCopilot.Services; +using AxCopilot.Themes; +using UglyToad.PdfPig; + +namespace AxCopilot.ViewModels; + +public partial class LauncherViewModel +{ + private string _previewText = ""; + private bool _hasPreview; + private CancellationTokenSource? _previewCts; + + public ObservableCollection QuickActionItems { get; } = new(); + public bool ShowQuickActions => string.IsNullOrEmpty(_inputText) && QuickActionItems.Count > 0; + + public string PreviewText + { + get => _previewText; + private set + { + _previewText = value; + OnPropertyChanged(); + } + } + + public bool HasPreview + { + get => _hasPreview; + private set + { + _hasPreview = value; + OnPropertyChanged(); + } + } + + public string? NavigateHistoryPrev() + { + var history = SearchHistoryService.GetAll(); + if (history.Count == 0) + return null; + + _historyIndex = Math.Min(_historyIndex + 1, history.Count - 1); + return history[_historyIndex]; + } + + public string? NavigateHistoryNext() + { + if (_historyIndex <= 0) + { + _historyIndex = -1; + return ""; + } + + _historyIndex--; + var history = SearchHistoryService.GetAll(); + return _historyIndex >= 0 && _historyIndex < history.Count + ? history[_historyIndex] + : ""; + } + + public void SetInputFromHistory(string text) + { + _isHistoryNavigation = true; + try + { + InputText = text; + } + finally + { + _isHistoryNavigation = false; + } + } + + public void LoadQuickActions() + { + QuickActionItems.Clear(); + + var topItems = UsageRankingService.GetTopItems(16); + var added = 0; + foreach (var (path, _) in topItems) + { + if (added >= 8) + break; + + var expanded = Environment.ExpandEnvironmentVariables(path); + var isFolder = Directory.Exists(expanded); + var isFile = !isFolder && File.Exists(expanded); + if (!isFolder && !isFile) + continue; + + var ext = Path.GetExtension(expanded).ToLowerInvariant(); + var title = Path.GetFileNameWithoutExtension(expanded); + if (string.IsNullOrEmpty(title)) + title = Path.GetFileName(expanded); + + var symbol = isFolder ? Symbols.Folder + : ext == ".exe" ? Symbols.App + : ext is ".lnk" or ".url" ? Symbols.App + : Symbols.File; + + var color = isFolder ? Color.FromRgb(0x10, 0x7C, 0x10) + : ext == ".exe" ? Color.FromRgb(0x4B, 0x5E, 0xFC) + : ext is ".lnk" or ".url" ? Color.FromRgb(0x4B, 0x5E, 0xFC) + : Color.FromRgb(0x5B, 0x4E, 0x7E); + + var bg = new SolidColorBrush(Color.FromArgb(0x26, color.R, color.G, color.B)); + QuickActionItems.Add(new QuickActionChip(title, symbol, path, bg)); + added++; + } + + OnPropertyChanged(nameof(ShowQuickActions)); + } + + private async Task UpdatePreviewAsync(LauncherItem? item) + { + _previewCts?.Cancel(); + _previewCts = new CancellationTokenSource(); + var ct = _previewCts.Token; + + if (item == null) + { + HasPreview = false; + PreviewText = string.Empty; + return; + } + + try + { + await Task.Delay(80, ct); + + if (item.Data is ClipboardEntry clipEntry && clipEntry.IsText) + { + var text = clipEntry.Text ?? string.Empty; + PreviewText = text.Length > 400 ? text[..400] + "…" : text; + HasPreview = !string.IsNullOrEmpty(PreviewText); + return; + } + + if (item.Data is IndexEntry indexEntry) + { + var path = Environment.ExpandEnvironmentVariables(indexEntry.Path); + var ext = Path.GetExtension(path).ToLowerInvariant(); + + if (IsPreviewableTextFile(ext) && File.Exists(path)) + { + var lines = await ReadFirstLinesAsync(path, 6, ct); + if (ct.IsCancellationRequested) + return; + + PreviewText = string.Join("\n", lines); + HasPreview = !string.IsNullOrWhiteSpace(PreviewText); + return; + } + + if (IsImageFile(ext) && File.Exists(path)) + { + var meta = await Task.Run(() => GetImageMeta(path), ct); + if (ct.IsCancellationRequested) + return; + + if (!string.IsNullOrEmpty(meta)) + { + PreviewText = meta; + HasPreview = true; + return; + } + } + + if (ext == ".pdf" && File.Exists(path)) + { + var meta = await Task.Run(() => GetPdfMeta(path), ct); + if (ct.IsCancellationRequested) + return; + + if (!string.IsNullOrEmpty(meta)) + { + PreviewText = meta; + HasPreview = true; + return; + } + } + + if (IsMediaFile(ext) && File.Exists(path)) + { + var meta = GetFileSizeMeta(path, ext); + if (!string.IsNullOrEmpty(meta)) + { + PreviewText = meta; + HasPreview = true; + return; + } + } + } + + PreviewText = string.Empty; + HasPreview = false; + } + catch (OperationCanceledException) + { + } + catch + { + PreviewText = string.Empty; + HasPreview = false; + } + } + + private static bool IsPreviewableTextFile(string ext) => ext is + ".txt" or ".md" or ".log" or ".csv" or ".json" or ".xml" + or ".yaml" or ".yml" or ".ini" or ".cfg" or ".conf" + or ".cs" or ".py" or ".js" or ".ts" or ".html" or ".css"; + + private static bool IsImageFile(string ext) => ext is + ".jpg" or ".jpeg" or ".png" or ".gif" or ".bmp" or ".webp" + or ".svg" or ".ico" or ".tiff" or ".tif"; + + private static bool IsMediaFile(string ext) => ext is + ".mp3" or ".wav" or ".flac" or ".aac" or ".ogg" or ".wma" + or ".mp4" or ".avi" or ".mkv" or ".mov" or ".wmv" or ".webm"; + + private static string? GetImageMeta(string path) + { + try + { + var fi = new FileInfo(path); + var size = FormatFileSize(fi.Length); + var ext = fi.Extension.TrimStart('.').ToUpperInvariant(); + + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var decoder = BitmapDecoder.Create( + stream, + BitmapCreateOptions.DelayCreation, + BitmapCacheOption.None); + var frame = decoder.Frames[0]; + + return $"이미지 {ext} · {frame.PixelWidth}x{frame.PixelHeight} · {size}\n수정: {fi.LastWriteTime:yyyy-MM-dd HH:mm}"; + } + catch + { + return null; + } + } + + private static string? GetPdfMeta(string path) + { + try + { + var fi = new FileInfo(path); + var size = FormatFileSize(fi.Length); + + using var doc = PdfDocument.Open(path); + var pages = doc.NumberOfPages; + var firstPageText = string.Empty; + if (pages > 0) + { + firstPageText = doc.GetPage(1).Text; + if (firstPageText.Length > 200) + firstPageText = firstPageText[..200] + "…"; + firstPageText = firstPageText.Replace("\r\n", " ").Replace("\n", " "); + } + + var meta = $"PDF · {pages}페이지 · {size}"; + if (!string.IsNullOrWhiteSpace(firstPageText)) + meta += $"\n{firstPageText}"; + return meta; + } + catch + { + return null; + } + } + + private static string? GetFileSizeMeta(string path, string ext) + { + try + { + var fi = new FileInfo(path); + var size = FormatFileSize(fi.Length); + var type = ext switch + { + ".mp3" or ".wav" or ".flac" or ".aac" or ".ogg" or ".wma" => "오디오", + ".mp4" or ".avi" or ".mkv" or ".mov" or ".wmv" or ".webm" => "동영상", + _ => "파일" + }; + + return $"{type} · {ext.TrimStart('.').ToUpperInvariant()} · {size}\n수정: {fi.LastWriteTime:yyyy-MM-dd HH:mm}"; + } + catch + { + return null; + } + } + + private static string FormatFileSize(long bytes) => bytes switch + { + < 1024 => $"{bytes} B", + < 1048576 => $"{bytes / 1024.0:F1} KB", + < 1073741824 => $"{bytes / 1048576.0:F1} MB", + _ => $"{bytes / 1073741824.0:F2} GB" + }; + + private static async Task> ReadFirstLinesAsync(string path, int maxLines, CancellationToken ct) + { + var lines = new List(); + using var reader = new StreamReader(path, System.Text.Encoding.UTF8, detectEncodingFromByteOrderMarks: true); + for (var i = 0; i < maxLines && !reader.EndOfStream; i++) + { + ct.ThrowIfCancellationRequested(); + var line = await reader.ReadLineAsync(ct); + if (line != null) + lines.Add(line); + } + + return lines; + } +} diff --git a/src/AxCopilot/ViewModels/LauncherViewModel.Widgets.cs b/src/AxCopilot/ViewModels/LauncherViewModel.Widgets.cs new file mode 100644 index 0000000..031e0d7 --- /dev/null +++ b/src/AxCopilot/ViewModels/LauncherViewModel.Widgets.cs @@ -0,0 +1,114 @@ +using AxCopilot.Handlers; +using AxCopilot.Services; +using System.IO; + +namespace AxCopilot.ViewModels; + +public partial class LauncherViewModel +{ + public string Widget_PerfText + { + get + { + var perf = PerformanceMonitorService.Instance; + return $"CPU {perf.CpuPercent:F0}% RAM {perf.RamPercent:F0}% {perf.DiskCText}"; + } + } + + public string Widget_PomoText + { + get + { + var pomo = PomodoroService.Instance; + var remain = pomo.Remaining; + var clock = $"{(int)remain.TotalMinutes:D2}:{remain.Seconds:D2}"; + return pomo.Mode switch + { + PomodoroMode.Focus => $"집중 {clock}", + PomodoroMode.Break => $"휴식 {clock}", + _ => $"대기 {clock}", + }; + } + } + + public bool Widget_PomoRunning => PomodoroService.Instance.IsRunning; + + private int _widgetNoteCount; + public string Widget_NoteText => _widgetNoteCount > 0 ? $"메모 {_widgetNoteCount}건" : "메모 없음"; + + public bool Widget_OllamaOnline => ServerStatusService.Instance.OllamaOnline; + public bool Widget_LlmOnline => ServerStatusService.Instance.LlmOnline; + public bool Widget_McpOnline => ServerStatusService.Instance.McpOnline; + public string Widget_McpName => ServerStatusService.Instance.McpName; + + private string _widgetWeatherText = "--"; + public string Widget_WeatherText + { + get => _widgetWeatherText; + internal set { _widgetWeatherText = value; OnPropertyChanged(); } + } + + public string Widget_CalText => + DateTime.Now.ToString("M/d (ddd)", System.Globalization.CultureInfo.GetCultureInfo("ko-KR")); + + private string _widgetBatteryText = "--"; + private string _widgetBatteryIcon = "\uE83F"; + private bool _widgetBatteryVisible; + + public string Widget_BatteryText + { + get => _widgetBatteryText; + internal set { _widgetBatteryText = value; OnPropertyChanged(); } + } + + public string Widget_BatteryIcon + { + get => _widgetBatteryIcon; + internal set { _widgetBatteryIcon = value; OnPropertyChanged(); } + } + + public bool Widget_BatteryVisible + { + get => _widgetBatteryVisible; + internal set { _widgetBatteryVisible = value; OnPropertyChanged(); } + } + + private int _widgetRefreshTick; + + public void UpdateWidgets() + { + _widgetRefreshTick++; + if (_widgetRefreshTick % 5 == 0) + _widgetNoteCount = GetNoteCount(); + + OnPropertyChanged(nameof(Widget_PerfText)); + OnPropertyChanged(nameof(Widget_PomoText)); + OnPropertyChanged(nameof(Widget_PomoRunning)); + OnPropertyChanged(nameof(Widget_NoteText)); + OnPropertyChanged(nameof(Widget_OllamaOnline)); + OnPropertyChanged(nameof(Widget_LlmOnline)); + OnPropertyChanged(nameof(Widget_McpOnline)); + OnPropertyChanged(nameof(Widget_McpName)); + OnPropertyChanged(nameof(Widget_CalText)); + } + + private static int GetNoteCount() + { + try + { + var notesFile = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "AxCopilot", + "notes.txt"); + if (!File.Exists(notesFile)) + return 0; + + return File.ReadLines(notesFile, System.Text.Encoding.UTF8) + .Count(line => !string.IsNullOrWhiteSpace(line)); + } + catch + { + return 0; + } + } +} diff --git a/src/AxCopilot/ViewModels/LauncherViewModel.cs b/src/AxCopilot/ViewModels/LauncherViewModel.cs index 9250523..472ba75 100644 --- a/src/AxCopilot/ViewModels/LauncherViewModel.cs +++ b/src/AxCopilot/ViewModels/LauncherViewModel.cs @@ -13,7 +13,7 @@ using AxCopilot.Themes; namespace AxCopilot.ViewModels; -public class LauncherViewModel : INotifyPropertyChanged +public partial class LauncherViewModel : INotifyPropertyChanged { private readonly CommandResolver _resolver; private readonly SettingsService _settings; @@ -21,6 +21,8 @@ public class LauncherViewModel : INotifyPropertyChanged private string _inputText = ""; private LauncherItem? _selectedItem; private bool _isLoading; + private bool _isHistoryNavigation; + private int _historyIndex = -1; private CancellationTokenSource? _searchCts; private System.Threading.Timer? _debounceTimer; private const int DebounceMs = 30; // 30ms 디바운스 — 연속 입력 시 중간 검색 스킵 @@ -48,6 +50,8 @@ public class LauncherViewModel : INotifyPropertyChanged { if (_inputText == value) return; _inputText = value; + if (!_isHistoryNavigation) + _historyIndex = -1; OnPropertyChanged(); OnPropertyChanged(nameof(HasActivePrefix)); OnPropertyChanged(nameof(ActivePrefixLabel)); @@ -56,6 +60,7 @@ public class LauncherViewModel : INotifyPropertyChanged OnPropertyChanged(nameof(IsClipboardMode)); OnPropertyChanged(nameof(ShowMergeHint)); OnPropertyChanged(nameof(MergeHintText)); + OnPropertyChanged(nameof(ShowQuickActions)); // 연속 입력 시 이전 검색 즉시 취소 + 50ms 디바운스 후 실제 검색 시작 _searchCts?.Cancel(); @@ -77,7 +82,12 @@ public class LauncherViewModel : INotifyPropertyChanged public LauncherItem? SelectedItem { get => _selectedItem; - set { _selectedItem = value; OnPropertyChanged(); } + set + { + _selectedItem = value; + OnPropertyChanged(); + _ = UpdatePreviewAsync(value); + } } public bool IsLoading @@ -245,7 +255,9 @@ public class LauncherViewModel : INotifyPropertyChanged if (IsActionMode) { IsActionMode = false; _actionSourceItem = null; } Results.Clear(); _lastSearchQuery = ""; + _historyIndex = -1; ClearMerge(); + LoadQuickActions(); } // ─── 검색 ──────────────────────────────────────────────────────────────── @@ -333,6 +345,9 @@ public class LauncherViewModel : INotifyPropertyChanged if (SelectedItem == null) return; + if (InputText.Trim().Length >= 2) + SearchHistoryService.Add(InputText.Trim()); + // 창을 먼저 닫아 체감 속도 확보 → 실행은 백그라운드 CloseRequested?.Invoke(this, EventArgs.Empty); diff --git a/src/AxCopilot/Views/LauncherWindow.Shell.cs b/src/AxCopilot/Views/LauncherWindow.Shell.cs new file mode 100644 index 0000000..6f02a75 --- /dev/null +++ b/src/AxCopilot/Views/LauncherWindow.Shell.cs @@ -0,0 +1,67 @@ +using System.Windows; +using System.Windows.Input; +using AxCopilot.Models; + +namespace AxCopilot.Views; + +public partial class LauncherWindow +{ + private QuickLookWindow? _quickLookWindow; + + private async void QuickActionChip_Click(object sender, MouseButtonEventArgs e) + { + if (((FrameworkElement)sender).DataContext is not QuickActionChip chip) + return; + + var expanded = Environment.ExpandEnvironmentVariables(chip.Path); + Hide(); + + try + { + await Task.Run(() => + System.Diagnostics.Process.Start( + new System.Diagnostics.ProcessStartInfo(expanded) + { + UseShellExecute = true + })); + _ = Task.Run(() => Services.UsageRankingService.RecordExecution(chip.Path)); + } + catch (Exception ex) + { + Services.LogService.Error($"빠른 실행 칩 열기 실패: {expanded} - {ex.Message}"); + } + } + + internal void ToggleQuickLook() + { + if (_quickLookWindow != null) + { + _quickLookWindow.Close(); + _quickLookWindow = null; + return; + } + + if (_vm.SelectedItem?.Data is not Services.IndexEntry indexEntry) + return; + + var path = Environment.ExpandEnvironmentVariables(indexEntry.Path); + if (!System.IO.File.Exists(path) && !System.IO.Directory.Exists(path)) + return; + + var qlLeft = Left + ActualWidth + 8; + var qlTop = Top; + + var screen = System.Windows.Forms.Screen.FromHandle( + new System.Windows.Interop.WindowInteropHelper(this).Handle); + if (qlLeft + 400 > screen.WorkingArea.Right) + qlLeft = Left - 408; + + _quickLookWindow = new QuickLookWindow(path, this) + { + Left = qlLeft, + Top = qlTop + }; + _quickLookWindow.Closed += (_, _) => _quickLookWindow = null; + _quickLookWindow.Show(); + } +} diff --git a/src/AxCopilot/Views/LauncherWindow.Widgets.cs b/src/AxCopilot/Views/LauncherWindow.Widgets.cs new file mode 100644 index 0000000..097c67b --- /dev/null +++ b/src/AxCopilot/Views/LauncherWindow.Widgets.cs @@ -0,0 +1,209 @@ +using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; +using AxCopilot.Services; + +namespace AxCopilot.Views; + +public partial class LauncherWindow +{ + private DispatcherTimer? _widgetTimer; + private static readonly SolidColorBrush DotOnline = new(Color.FromRgb(0x10, 0xB9, 0x81)); + private static readonly SolidColorBrush DotOffline = new(Color.FromRgb(0x9E, 0x9E, 0x9E)); + private int _widgetBatteryTick; + private int _widgetWeatherTick; + + internal void StartWidgetUpdates() + { + var settings = CurrentApp?.SettingsService?.Settings; + + PerformanceMonitorService.Instance.StartPolling(); + ServerStatusService.Instance.Start(settings); + PomodoroService.Instance.StateChanged -= OnPomoStateChanged; + PomodoroService.Instance.StateChanged += OnPomoStateChanged; + ServerStatusService.Instance.StatusChanged -= OnServerStatusChanged; + ServerStatusService.Instance.StatusChanged += OnServerStatusChanged; + + _vm.UpdateWidgets(); + UpdateServerDots(); + UpdateBatteryWidget(); + _ = RefreshWeatherAsync(); + + if (_widgetTimer == null) + { + _widgetTimer = new DispatcherTimer(DispatcherPriority.Background) + { + Interval = TimeSpan.FromSeconds(1) + }; + _widgetTimer.Tick += (_, _) => + { + _vm.UpdateWidgets(); + UpdateServerDots(); + if (_vm.Widget_PerfText.Length > 0 && _widgetBatteryTick++ % 30 == 0) + UpdateBatteryWidget(); + if (_widgetWeatherTick++ % 120 == 0) + _ = RefreshWeatherAsync(); + }; + } + + _widgetTimer.Start(); + UpdatePomoWidgetStyle(); + } + + internal void StopWidgetUpdates() + { + _widgetTimer?.Stop(); + PerformanceMonitorService.Instance.StopPolling(); + PomodoroService.Instance.StateChanged -= OnPomoStateChanged; + ServerStatusService.Instance.StatusChanged -= OnServerStatusChanged; + } + + private void OnPomoStateChanged(object? sender, EventArgs e) + { + Dispatcher.InvokeAsync(() => + { + _vm.UpdateWidgets(); + UpdatePomoWidgetStyle(); + }); + } + + private void OnServerStatusChanged(object? sender, EventArgs e) + => Dispatcher.InvokeAsync(UpdateServerDots); + + private void UpdateServerDots() + { + var server = ServerStatusService.Instance; + if (OllamaStatusDot != null) + OllamaStatusDot.Fill = server.OllamaOnline ? DotOnline : DotOffline; + if (LlmStatusDot != null) + LlmStatusDot.Fill = server.LlmOnline ? DotOnline : DotOffline; + if (McpStatusDot != null) + McpStatusDot.Fill = server.McpOnline ? DotOnline : DotOffline; + } + + private void UpdatePomoWidgetStyle() + { + if (WgtPomo == null) + return; + + var running = PomodoroService.Instance.IsRunning; + WgtPomo.Background = running + ? new SolidColorBrush(Color.FromArgb(0x1E, 0xF5, 0x9E, 0x0B)) + : new SolidColorBrush(Color.FromArgb(0x0D, 0xF5, 0x9E, 0x0B)); + } + + private void WgtPerf_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _vm.InputText = "info "; + InputBox?.Focus(); + } + + private void WgtPomo_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _vm.InputText = "pomo "; + InputBox?.Focus(); + } + + private void WgtNote_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _vm.InputText = "note "; + InputBox?.Focus(); + } + + private void WgtServer_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + var settings = CurrentApp?.SettingsService?.Settings; + ServerStatusService.Instance.Refresh(settings); + _vm.InputText = "port"; + InputBox?.Focus(); + } + + private void WgtWeather_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + var internalMode = CurrentApp?.SettingsService?.Settings.InternalModeEnabled ?? true; + if (!internalMode) + { + try + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("https://wttr.in") { UseShellExecute = true }); + } + catch (Exception ex) + { + LogService.Warn($"날씨 페이지 열기 실패: {ex.Message}"); + } + } + else + { + WeatherWidgetService.Invalidate(); + _ = RefreshWeatherAsync(); + } + } + + private void WgtCal_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + try + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("ms-clock:") { UseShellExecute = true }); + } + catch + { + try + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("outlookcal:") { UseShellExecute = true }); + } + catch (Exception ex) + { + LogService.Warn($"달력 열기 실패: {ex.Message}"); + } + } + } + + private void WgtBattery_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + try + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("ms-settings:powersleep") { UseShellExecute = true }); + } + catch (Exception ex) + { + LogService.Warn($"전원 설정 열기 실패: {ex.Message}"); + } + } + + private void UpdateBatteryWidget() + { + try + { + var power = System.Windows.Forms.SystemInformation.PowerStatus; + var pct = power.BatteryLifePercent; + if (pct > 1.0f || pct < 0f) + { + _vm.Widget_BatteryVisible = false; + return; + } + + _vm.Widget_BatteryVisible = true; + var pctInt = (int)(pct * 100); + var charging = power.PowerLineStatus == System.Windows.Forms.PowerLineStatus.Online; + _vm.Widget_BatteryText = charging ? $"{pctInt}% 충전" : $"{pctInt}%"; + _vm.Widget_BatteryIcon = charging ? "\uE83E" + : pctInt >= 85 ? "\uEBA7" + : pctInt >= 70 ? "\uEBA5" + : pctInt >= 50 ? "\uEBA3" + : pctInt >= 25 ? "\uEBA1" + : "\uEBA0"; + } + catch (Exception ex) + { + LogService.Warn($"배터리 위젯 갱신 실패: {ex.Message}"); + _vm.Widget_BatteryVisible = false; + } + } + + private async Task RefreshWeatherAsync() + { + var internalMode = CurrentApp?.SettingsService?.Settings.InternalModeEnabled ?? true; + await WeatherWidgetService.RefreshAsync(internalMode); + await Dispatcher.InvokeAsync(() => { _vm.Widget_WeatherText = WeatherWidgetService.CachedText; }); + } +} diff --git a/src/AxCopilot/Views/LauncherWindow.xaml b/src/AxCopilot/Views/LauncherWindow.xaml index 30b963c..6d71c95 100644 --- a/src/AxCopilot/Views/LauncherWindow.xaml +++ b/src/AxCopilot/Views/LauncherWindow.xaml @@ -18,6 +18,8 @@ WindowStartupLocation="Manual" Loaded="Window_Loaded" Deactivated="Window_Deactivated" + LocationChanged="Window_LocationChanged" + IsVisibleChanged="Window_IsVisibleChanged" PreviewKeyDown="Window_PreviewKeyDown" KeyDown="Window_KeyDown"> @@ -201,10 +203,16 @@ + + + + + + @@ -413,8 +421,57 @@ Foreground="{DynamicResource HintText}" Margin="3,0,0,0" VerticalAlignment="Center"/> - - + + + + + + + + + + + + + + + + + + + + + + + @@ -691,9 +748,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AxCopilot/Views/QuickLookWindow.xaml.cs b/src/AxCopilot/Views/QuickLookWindow.xaml.cs new file mode 100644 index 0000000..5f8320d --- /dev/null +++ b/src/AxCopilot/Views/QuickLookWindow.xaml.cs @@ -0,0 +1,143 @@ +using System.IO; +using System.Text; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media.Imaging; +using UglyToad.PdfPig; + +namespace AxCopilot.Views; + +public partial class QuickLookWindow : Window +{ + private static readonly HashSet ImageExts = new(StringComparer.OrdinalIgnoreCase) + { + ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".ico", ".tiff", ".tif" + }; + + private static readonly HashSet TextExts = new(StringComparer.OrdinalIgnoreCase) + { + ".txt", ".md", ".cs", ".vb", ".fs", ".py", ".js", ".ts", ".jsx", ".tsx", + ".json", ".xml", ".xaml", ".yaml", ".yml", ".toml", ".ini", ".conf", + ".log", ".csv", ".html", ".htm", ".css", ".scss", ".less", + ".sql", ".sh", ".bash", ".bat", ".cmd", ".ps1", + ".config", ".env", ".gitignore", ".editorconfig", + ".java", ".cpp", ".c", ".h", ".hpp", ".rs", ".go", ".rb", ".php", ".swift", + ".vue", ".svelte", ".dockerfile" + }; + + public QuickLookWindow(string path, Window owner) + { + InitializeComponent(); + Owner = owner; + KeyDown += OnKeyDown; + Loaded += (_, _) => LoadPreview(path); + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is Key.Escape or Key.F3) + { + Close(); + e.Handled = true; + } + } + + private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + DragMove(); + } + + private void BtnClose_Click(object sender, MouseButtonEventArgs e) => Close(); + + private void LoadPreview(string path) + { + try + { + FileNameText.Text = Path.GetFileName(path); + + if (Directory.Exists(path)) + { + ShowInfo("\uE838", "폴더", path); + return; + } + + if (!File.Exists(path)) + { + ShowInfo("\uE783", "파일을 찾을 수 없습니다.", path); + return; + } + + var info = new FileInfo(path); + var ext = Path.GetExtension(path); + FooterPath.Text = path; + FooterMeta.Text = $"{FormatSize(info.Length)} · {info.LastWriteTime:yyyy-MM-dd HH:mm}"; + + if (ImageExts.Contains(ext)) + { + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var image = new BitmapImage(); + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = fs; + image.EndInit(); + image.Freeze(); + + PreviewImage.Source = image; + ImageScrollViewer.Visibility = Visibility.Visible; + FileTypeIcon.Text = "\uE91B"; + return; + } + + if (string.Equals(ext, ".pdf", StringComparison.OrdinalIgnoreCase)) + { + using var doc = PdfDocument.Open(path); + var page = doc.NumberOfPages > 0 ? doc.GetPage(1).Text : ""; + PdfPreviewText.Text = page.Length > 1200 ? page[..1200] + "…" : page; + PdfScrollViewer.Visibility = Visibility.Visible; + FileTypeIcon.Text = "\uEA90"; + return; + } + + if (TextExts.Contains(ext)) + { + string text; + try + { + text = File.ReadAllText(path, Encoding.UTF8); + } + catch + { + text = File.ReadAllText(path); + } + + PreviewText.Text = text.Length > 4000 ? text[..4000] + "\n…" : text; + TextScrollViewer.Visibility = Visibility.Visible; + FileTypeIcon.Text = "\uE8A5"; + return; + } + + ShowInfo("\uE7C3", $"파일 · {ext.TrimStart('.').ToUpperInvariant()}", path); + } + catch (Exception ex) + { + ShowInfo("\uEA39", "미리보기를 열지 못했습니다.", ex.Message); + } + } + + private void ShowInfo(string icon, string title, string detail) + { + InfoTypeIcon.Text = icon; + InfoTypeName.Text = title; + InfoSubText.Text = detail; + InfoPanel.Visibility = Visibility.Visible; + } + + private static string FormatSize(long bytes) => bytes switch + { + < 1024 => $"{bytes} B", + < 1048576 => $"{bytes / 1024.0:F1} KB", + < 1073741824 => $"{bytes / 1048576.0:F1} MB", + _ => $"{bytes / 1073741824.0:F2} GB" + }; +}