From 1f52bc1cc37129a2e5fcca4f21ab97b16734f54b Mon Sep 17 00:00:00 2001 From: lacvet Date: Mon, 6 Apr 2026 19:43:18 +0900 Subject: [PATCH] =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=B0=BD=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=9F=B0=EC=B2=98=20=EC=83=89=EC=9D=B8=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EB=A5=A0=EA=B3=BC=20=EB=82=A8=EC=9D=80=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9D=84=20=EC=A7=81=EC=A0=91=20=ED=91=9C=EC=8B=9C=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IndexService에 LauncherIndexProgressInfo와 진행 이벤트를 추가해 전체 색인 중 진행률, 상태 문구, 예상 남은 시간을 계산하도록 변경 - 설정창 일반 탭의 인덱싱 속도 아래에 프로그레스바와 상태/상세 문구 패널을 추가하고 실시간으로 바인딩되도록 연결 - 전체 색인 완료 후 최근 색인 완료 요약과 검색 가능 항목 수를 같은 위치에 유지해 런처 하단 완료 문구 의존도를 낮춤 - README와 DEVELOPMENT 문서에 변경 이력 및 반영 시각을 기록 검증 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\ - 경고 0 / 오류 0 --- README.md | 6 ++ docs/DEVELOPMENT.md | 3 + src/AxCopilot/Services/IndexService.cs | 88 ++++++++++++++++++++-- src/AxCopilot/Views/SettingsWindow.xaml | 20 +++++ src/AxCopilot/Views/SettingsWindow.xaml.cs | 36 +++++++++ 5 files changed, 148 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ddc960b..2bf7987 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-06 19:24 (KST) +- 런처 색인 진행 상태를 런처 하단 완료 문구 대신 설정창 `인덱싱 속도` 아래에서 확인할 수 있게 바꿨습니다. 전체 색인 중에는 진행률 바와 상태/남은 시간 문구가 보이고, 완료 후에는 최근 색인 결과가 같은 위치에 남습니다. + - 업데이트: 2026-04-06 15:31 (KST) - 코워크/코드 하단 우측의 권한 요청 버튼은 footer 작업 바와 더 자연스럽게 이어지도록 외곽 테두리를 제거해 플랫한 형태로 정리했습니다. @@ -17,6 +20,9 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 - 업데이트: 2026-04-06 19:02 (KST) - 설정-테마의 글로우 관련 항목이 저장만 되고 열린 창에 바로 반영되지 않던 문제를 수정했습니다. 런처는 설정 변경 이벤트를 직접 받아 테두리/아이콘 애니메이션/무지개 글로우/선택 글로우를 즉시 다시 적용하고, AX Agent도 스트리밍 중 입력창 글로우 설정을 바로 반영합니다. +- 업데이트: 2026-04-06 19:11 (KST) +- 런처 색인 상태 문구가 오래 남던 문제를 수정했습니다. 파일 watcher의 증분 갱신은 더 이상 `색인 완료` 이벤트를 다시 올리지 않고, 실제 전체 색인 완료 때만 런처 상태 문구가 갱신되도록 분리했습니다. + - 업데이트: 2026-04-06 15:26 (KST) - AX Agent 채팅/코워크/코드 하단 안내 문구를 현재 구현 기준으로 다시 정리했습니다. 입력창 워터마크는 탭 종류와 작업 폴더 선택 여부에 따라 실제 가능한 작업을 더 정확히 안내합니다. - 선택된 프리셋 안내도 placeholder 문구 대신 실제 설명 중심으로 정리해, 프리셋 설명 카드와 입력창 워터마크가 서로 다른 역할로 보이도록 맞췄습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index cb39ab5..73e12f1 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,9 +1,12 @@ # AX Copilot - 媛쒕컻 臾몄꽌 +- Document update: 2026-04-06 19:24 (KST) - Added `LauncherIndexProgressInfo` to `IndexService` so full launcher indexing now emits real progress/status updates, including estimated remaining time during path scans and a persisted completion summary after the run ends. +- Document update: 2026-04-06 19:24 (KST) - Moved the user-facing launcher index progress surface into `SettingsWindow` under the index-speed control. The settings screen now shows a progress bar plus status/detail text while indexing is active and keeps the latest completion summary visible afterward. - Document update: 2026-04-06 18:46 (KST) - Tightened launcher indexing for real-world projects by ignoring `.git`, `.vs`, `node_modules`, `bin`, `obj`, `dist`, `packages`, `venv`, `__pycache__`, and `target` directories during both watcher handling and full scans. The launcher now skips noisy build/cache trees instead of reacting to them like searchable content. - Document update: 2026-04-06 18:46 (KST) - Replaced the launcher's recursive `EnumerateFiles(..., AllDirectories)` walk with an ignore-aware manual traversal so large repos avoid descending into dependency and build folders, and slowed the rainbow glow timer from `20ms` to `40ms`. - Document update: 2026-04-06 18:46 (KST) - Added a deferred responsive-layout timer to `ChatWindow` so AX Agent no longer calls `RenderMessages()` on every `SizeChanged` event. Resizing and layout changes are now coalesced into a single update after a short delay, reducing the heavy-window feel during agent-window manipulation. - Document update: 2026-04-06 19:02 (KST) - Fixed theme glow settings so they apply to already-open windows instead of only affecting the next open. `LauncherWindow` now listens to `SettingsChanged` and reapplies border, icon animation, rainbow glow, and selection glow immediately, while `ChatWindow.RefreshFromSavedSettings()` now re-evaluates chat rainbow glow during active streaming. +- Document update: 2026-04-06 19:11 (KST) - Fixed the launcher index-status banner so it no longer lingers after indexing has effectively finished. Incremental watcher updates now refresh the in-memory/persisted index silently, and only full rebuild completion raises the visible `IndexRebuilt` signal consumed by `LauncherWindow`. - Document update: 2026-04-06 15:31 (KST) - Removed the visible outer border from the Cowork/Code footer permission selector so it reads as a flatter inline action in the bottom work bar. - Document update: 2026-04-06 15:26 (KST) - Reworked bottom guidance copy for Chat/Cowork/Code around the actual AX Agent behavior. Input watermark text now comes from a shared helper that considers the active tab and whether a work folder is selected, instead of using overly broad static text. - Document update: 2026-04-06 15:26 (KST) - Adjusted the selected preset guide so it prefers concise preset descriptions over raw placeholder prompts, keeping the footer guide and the composer watermark in distinct roles. diff --git a/src/AxCopilot/Services/IndexService.cs b/src/AxCopilot/Services/IndexService.cs index e239091..d906903 100644 --- a/src/AxCopilot/Services/IndexService.cs +++ b/src/AxCopilot/Services/IndexService.cs @@ -48,9 +48,11 @@ public class IndexService : IDisposable public IReadOnlyList Entries => _index; public event EventHandler? IndexRebuilt; + public event EventHandler? IndexProgressChanged; public TimeSpan LastIndexDuration { get; private set; } public int LastIndexCount { get; private set; } public bool HasCachedIndexLoaded { get; private set; } + public LauncherIndexProgressInfo CurrentProgress { get; private set; } = LauncherIndexProgressInfo.Idle(); public IndexService(SettingsService settings) { @@ -110,16 +112,30 @@ public class IndexService : IDisposable try { var fileSystemEntries = new List(); - var paths = GetExpandedIndexPaths(); + var paths = GetExpandedIndexPaths() + .Where(Directory.Exists) + .ToList(); var allowedExts = GetAllowedExtensions(); var indexSpeed = _settings.Settings.IndexSpeed ?? "normal"; - foreach (var dir in paths) + ReportIndexProgress(LauncherIndexProgressInfo.Running(0, "색인 준비 중...", "남은 시간 계산 중")); + + for (var index = 0; index < paths.Count; index++) { - if (!Directory.Exists(dir)) - continue; + var dir = paths[index]; + var progressBase = paths.Count == 0 ? 0 : (double)index / paths.Count; + ReportIndexProgress(LauncherIndexProgressInfo.Running( + progressBase * 100, + $"스캔 중: {Path.GetFileName(dir)}", + EstimateRemainingText(progressBase, sw.Elapsed))); await ScanDirectoryAsync(dir, fileSystemEntries, allowedExts, indexSpeed, ct); + + var completedProgress = paths.Count == 0 ? 1 : (double)(index + 1) / paths.Count; + ReportIndexProgress(LauncherIndexProgressInfo.Running( + completedProgress * 100, + $"스캔 완료: {Path.GetFileName(dir)}", + EstimateRemainingText(completedProgress, sw.Elapsed))); } ComputeAllSearchCaches(fileSystemEntries); @@ -135,6 +151,10 @@ public class IndexService : IDisposable LastIndexCount = _index.Count; HasCachedIndexLoaded = fileSystemEntries.Count > 0; PersistCache(); + ReportIndexProgress(LauncherIndexProgressInfo.Completed( + LastIndexCount, + LastIndexDuration, + $"최근 색인 완료 · {LastIndexCount:N0}개 항목 · {LastIndexDuration.TotalSeconds:F1}초")); LogService.Info($"런처 인덱싱 완료: {fileSystemEntries.Count}개 파일 시스템 항목 ({sw.Elapsed.TotalSeconds:F1}초)"); IndexRebuilt?.Invoke(this, EventArgs.Empty); } @@ -291,10 +311,34 @@ public class IndexService : IDisposable PersistCacheDeferred(); LastIndexCount = _index.Count; - IndexRebuilt?.Invoke(this, EventArgs.Empty); + CurrentProgress = LauncherIndexProgressInfo.Completed( + LastIndexCount, + LastIndexDuration, + $"최근 색인 완료 · {LastIndexCount:N0}개 항목"); LogService.Info($"런처 인덱스 증분 갱신: {triggerPath}"); } + private void ReportIndexProgress(LauncherIndexProgressInfo progress) + { + CurrentProgress = progress; + IndexProgressChanged?.Invoke(this, progress); + } + + private static string EstimateRemainingText(double progressRatio, TimeSpan elapsed) + { + if (progressRatio <= 0.01) + return "남은 시간 계산 중"; + + var remaining = TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds * ((1 - progressRatio) / progressRatio)); + if (remaining.TotalSeconds < 1) + return "곧 완료"; + + if (remaining.TotalMinutes >= 1) + return $"예상 남은 시간 {remaining.Minutes + (remaining.Hours * 60)}분 {remaining.Seconds}초"; + + return $"예상 남은 시간 {Math.Max(1, (int)Math.Round(remaining.TotalSeconds))}초"; + } + private bool AddPathEntryIncrementally(string fullPath, string rootPath) { if (ShouldIgnorePath(fullPath)) @@ -926,3 +970,37 @@ public enum IndexEntryType Alias, Folder } + +public sealed class LauncherIndexProgressInfo +{ + public bool IsRunning { get; init; } + public double ProgressPercent { get; init; } + public string StatusText { get; init; } = ""; + public string DetailText { get; init; } = ""; + + public static LauncherIndexProgressInfo Idle() => new() + { + IsRunning = false, + ProgressPercent = 0, + StatusText = "색인 대기 중", + DetailText = "필요할 때 전체 색인을 시작합니다." + }; + + public static LauncherIndexProgressInfo Running(double progressPercent, string statusText, string detailText) => new() + { + IsRunning = true, + ProgressPercent = Math.Clamp(progressPercent, 0, 100), + StatusText = statusText, + DetailText = detailText + }; + + public static LauncherIndexProgressInfo Completed(int count, TimeSpan duration, string statusText) => new() + { + IsRunning = false, + ProgressPercent = 100, + StatusText = statusText, + DetailText = count > 0 + ? $"현재 검색 가능 항목 {count:N0}개 · 최근 전체 색인 {duration.TotalSeconds:F1}초" + : "검색 가능한 항목이 없습니다." + }; +} diff --git a/src/AxCopilot/Views/SettingsWindow.xaml b/src/AxCopilot/Views/SettingsWindow.xaml index 00578f6..726e445 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml +++ b/src/AxCopilot/Views/SettingsWindow.xaml @@ -1056,6 +1056,26 @@ + + + + + + + + diff --git a/src/AxCopilot/Views/SettingsWindow.xaml.cs b/src/AxCopilot/Views/SettingsWindow.xaml.cs index 00a8b01..e7c9abb 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml.cs +++ b/src/AxCopilot/Views/SettingsWindow.xaml.cs @@ -15,6 +15,7 @@ public partial class SettingsWindow : Window private readonly SettingsViewModel _vm; private readonly Action _previewCallback; private readonly Action _revertCallback; + private IndexService? _indexService; private bool _saved; private bool _isDisplayModeSyncing; @@ -59,9 +60,15 @@ public partial class SettingsWindow : Window catch { /* 인덱싱 실패해도 설정 저장은 완료 */ } }); }; + Closed += (_, _) => + { + if (_indexService != null) + _indexService.IndexProgressChanged -= IndexService_IndexProgressChanged; + }; Loaded += async (_, _) => { + BindIndexProgress(); RefreshHotkeyBadges(); SetVersionText(); EnsureHotkeyInCombo(); @@ -104,6 +111,35 @@ public partial class SettingsWindow : Window }; } + private void BindIndexProgress() + { + _indexService = (System.Windows.Application.Current as App)?.IndexService; + if (_indexService == null) + return; + + _indexService.IndexProgressChanged -= IndexService_IndexProgressChanged; + _indexService.IndexProgressChanged += IndexService_IndexProgressChanged; + ApplyIndexProgress(_indexService.CurrentProgress); + } + + private void IndexService_IndexProgressChanged(object? sender, LauncherIndexProgressInfo e) + { + Dispatcher.BeginInvoke(() => ApplyIndexProgress(e)); + } + + private void ApplyIndexProgress(LauncherIndexProgressInfo progress) + { + if (IndexProgressPanel == null || IndexProgressStatusText == null || IndexProgressBar == null || IndexProgressDetailText == null) + return; + + IndexProgressPanel.Visibility = Visibility.Visible; + IndexProgressStatusText.Text = progress.StatusText; + IndexProgressDetailText.Text = progress.DetailText; + IndexProgressBar.IsIndeterminate = progress.IsRunning && progress.ProgressPercent <= 0.01; + if (!IndexProgressBar.IsIndeterminate) + IndexProgressBar.Value = progress.ProgressPercent; + } + private bool HasLegacyAgentTab() => MainSettingsTab != null && AgentTabItem != null && MainSettingsTab.Items.Contains(AgentTabItem);