설정창에서 런처 색인 진행률과 남은 시간을 직접 표시하도록 개선
Some checks failed
Release Gate / gate (push) Has been cancelled

- IndexService에 LauncherIndexProgressInfo와 진행 이벤트를 추가해 전체 색인 중 진행률, 상태 문구, 예상 남은 시간을 계산하도록 변경
- 설정창 일반 탭의 인덱싱 속도 아래에 프로그레스바와 상태/상세 문구 패널을 추가하고 실시간으로 바인딩되도록 연결
- 전체 색인 완료 후 최근 색인 완료 요약과 검색 가능 항목 수를 같은 위치에 유지해 런처 하단 완료 문구 의존도를 낮춤
- README와 DEVELOPMENT 문서에 변경 이력 및 반영 시각을 기록

검증
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\
- 경고 0 / 오류 0
This commit is contained in:
2026-04-06 19:43:18 +09:00
parent cc12177252
commit 1f52bc1cc3
5 changed files with 148 additions and 5 deletions

View File

@@ -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 문구 대신 실제 설명 중심으로 정리해, 프리셋 설명 카드와 입력창 워터마크가 서로 다른 역할로 보이도록 맞췄습니다.

View File

@@ -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.

View File

@@ -48,9 +48,11 @@ public class IndexService : IDisposable
public IReadOnlyList<IndexEntry> Entries => _index;
public event EventHandler? IndexRebuilt;
public event EventHandler<LauncherIndexProgressInfo>? 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<IndexEntry>();
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}초"
: "검색 가능한 항목이 없습니다."
};
}

View File

@@ -1056,6 +1056,26 @@
</Grid>
</Border>
<Border x:Name="IndexProgressPanel"
Style="{StaticResource SettingsRow}"
Visibility="Collapsed">
<StackPanel>
<TextBlock x:Name="IndexProgressStatusText"
Text="색인 대기 중"
Style="{StaticResource RowLabel}"/>
<ProgressBar x:Name="IndexProgressBar"
Height="8"
Margin="0,10,0,0"
Minimum="0"
Maximum="100"
Value="0"/>
<TextBlock x:Name="IndexProgressDetailText"
Text="필요할 때 전체 색인을 시작합니다."
Margin="0,8,0,0"
Style="{StaticResource RowHint}"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
<!-- 알림 패널 (하위 탭) — 기존 알림 탭 내용을 여기로 통합 -->

View File

@@ -15,6 +15,7 @@ public partial class SettingsWindow : Window
private readonly SettingsViewModel _vm;
private readonly Action<string> _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);