설정창에서 런처 색인 진행률과 남은 시간을 직접 표시하도록 개선
Some checks failed
Release Gate / gate (push) Has been cancelled
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:
@@ -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 문구 대신 실제 설명 중심으로 정리해, 프리셋 설명 카드와 입력창 워터마크가 서로 다른 역할로 보이도록 맞췄습니다.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}초"
|
||||
: "검색 가능한 항목이 없습니다."
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
<!-- 알림 패널 (하위 탭) — 기존 알림 탭 내용을 여기로 통합 -->
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user