- 런처 색인에서 .git, node_modules, bin, obj, dist, packages, venv, __pycache__, target 경로를 스캔/감시 제외해 유휴 CPU와 재색인 부담을 줄임 - 런처 재귀 파일 스캔을 제외 폴더 인지형 순회로 바꾸고 무지개 글로우 타이머 주기를 낮춰 visible 상태 UI 갱신 비용을 완화함 - AX Agent는 SizeChanged 때마다 전체 RenderMessages를 즉시 호출하지 않고 디바운스 후 한 번만 반영하도록 바꿔 창 조작 시 무거운 느낌을 줄임 - README와 DEVELOPMENT 문서를 2026-04-06 18:46 (KST) 기준으로 갱신하고 Release 빌드에서 경고 0 / 오류 0을 확인함
This commit is contained in:
@@ -10,6 +10,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
||||
- 업데이트: 2026-04-06 15:31 (KST)
|
||||
- 코워크/코드 하단 우측의 권한 요청 버튼은 footer 작업 바와 더 자연스럽게 이어지도록 외곽 테두리를 제거해 플랫한 형태로 정리했습니다.
|
||||
|
||||
- 업데이트: 2026-04-06 18:46 (KST)
|
||||
- 런처 색인에서 `.git`, `node_modules`, `bin`, `obj`, `dist`, `packages`, `venv`, `__pycache__`, `target` 같은 무거운 폴더를 스캔/감시 대상에서 제외해 유휴 CPU와 재색인 부담을 줄였습니다.
|
||||
- 런처 무지개 글로우 갱신 주기를 낮추고, AX Agent는 창 크기 변화 시 메시지 전체 재렌더를 짧게 묶어서 한 번만 반영하도록 바꿔 창 조작 시 버벅이는 느낌을 줄였습니다.
|
||||
|
||||
- 업데이트: 2026-04-06 15:26 (KST)
|
||||
- AX Agent 채팅/코워크/코드 하단 안내 문구를 현재 구현 기준으로 다시 정리했습니다. 입력창 워터마크는 탭 종류와 작업 폴더 선택 여부에 따라 실제 가능한 작업을 더 정확히 안내합니다.
|
||||
- 선택된 프리셋 안내도 placeholder 문구 대신 실제 설명 중심으로 정리해, 프리셋 설명 카드와 입력창 워터마크가 서로 다른 역할로 보이도록 맞췄습니다.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# AX Copilot - 媛쒕컻 臾몄꽌
|
||||
|
||||
- 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 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.
|
||||
|
||||
@@ -13,6 +13,21 @@ namespace AxCopilot.Services;
|
||||
/// </summary>
|
||||
public class IndexService : IDisposable
|
||||
{
|
||||
private static readonly HashSet<string> IgnoredDirectoryNames = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".git",
|
||||
".vs",
|
||||
"node_modules",
|
||||
"bin",
|
||||
"obj",
|
||||
"dist",
|
||||
"packages",
|
||||
"__pycache__",
|
||||
".venv",
|
||||
"venv",
|
||||
"target"
|
||||
};
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
private readonly List<FileSystemWatcher> _watchers = new();
|
||||
private readonly object _timerLock = new();
|
||||
@@ -190,6 +205,9 @@ public class IndexService : IDisposable
|
||||
|
||||
private void OnWatcherEvent(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (ShouldIgnorePath(e.FullPath))
|
||||
return;
|
||||
|
||||
var rootPath = (sender as FileSystemWatcher)?.Path;
|
||||
if (TryApplyIncrementalChange(e.ChangeType, e.FullPath, rootPath))
|
||||
return;
|
||||
@@ -199,6 +217,9 @@ public class IndexService : IDisposable
|
||||
|
||||
private void OnWatcherRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
if (ShouldIgnorePath(e.FullPath) && ShouldIgnorePath(e.OldFullPath))
|
||||
return;
|
||||
|
||||
var rootPath = (sender as FileSystemWatcher)?.Path;
|
||||
if (TryApplyIncrementalRename(e.OldFullPath, e.FullPath, rootPath))
|
||||
return;
|
||||
@@ -276,6 +297,9 @@ public class IndexService : IDisposable
|
||||
|
||||
private bool AddPathEntryIncrementally(string fullPath, string rootPath)
|
||||
{
|
||||
if (ShouldIgnorePath(fullPath))
|
||||
return false;
|
||||
|
||||
var updated = false;
|
||||
|
||||
lock (_indexLock)
|
||||
@@ -307,6 +331,9 @@ public class IndexService : IDisposable
|
||||
|
||||
private bool RemovePathEntriesIncrementally(string fullPath)
|
||||
{
|
||||
if (ShouldIgnorePath(fullPath))
|
||||
return false;
|
||||
|
||||
var normalizedPath = NormalizePath(fullPath);
|
||||
lock (_indexLock)
|
||||
{
|
||||
@@ -340,6 +367,9 @@ public class IndexService : IDisposable
|
||||
|
||||
private void ScheduleRebuild(string triggerPath)
|
||||
{
|
||||
if (ShouldIgnorePath(triggerPath))
|
||||
return;
|
||||
|
||||
LogService.Info($"파일 변경 감지: {triggerPath} — {RebuildDebounceMs}ms 후 전체 재빌드 예약");
|
||||
lock (_timerLock)
|
||||
{
|
||||
@@ -686,7 +716,7 @@ public class IndexService : IDisposable
|
||||
try
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
|
||||
foreach (var file in EnumerateFilesWithIgnoredDirectories(dir))
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -704,7 +734,7 @@ public class IndexService : IDisposable
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var name = Path.GetFileName(subDir);
|
||||
if (name.StartsWith(".", StringComparison.Ordinal))
|
||||
if (name.StartsWith(".", StringComparison.Ordinal) || IgnoredDirectoryNames.Contains(name))
|
||||
continue;
|
||||
|
||||
entries.Add(CreateFolderEntry(subDir));
|
||||
@@ -717,6 +747,78 @@ public class IndexService : IDisposable
|
||||
}, ct);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateFilesWithIgnoredDirectories(string rootDir)
|
||||
{
|
||||
var pending = new Stack<string>();
|
||||
pending.Push(rootDir);
|
||||
|
||||
while (pending.Count > 0)
|
||||
{
|
||||
var current = pending.Pop();
|
||||
|
||||
IEnumerable<string> files;
|
||||
try
|
||||
{
|
||||
files = Directory.EnumerateFiles(current, "*.*", SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
yield return file;
|
||||
|
||||
IEnumerable<string> directories;
|
||||
try
|
||||
{
|
||||
directories = Directory.EnumerateDirectories(current, "*", SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
if (ShouldIgnorePath(directory))
|
||||
continue;
|
||||
|
||||
pending.Push(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldIgnorePath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var normalized = NormalizePath(path);
|
||||
foreach (var segment in normalized.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
|
||||
{
|
||||
if (IgnoredDirectoryNames.Contains(segment))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IndexEntry CreateFileEntry(string filePath)
|
||||
{
|
||||
var ext = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
|
||||
@@ -93,6 +93,7 @@ public partial class ChatWindow : Window
|
||||
private readonly DispatcherTimer _conversationPersistTimer;
|
||||
private readonly DispatcherTimer _agentUiEventTimer;
|
||||
private readonly DispatcherTimer _tokenUsagePopupCloseTimer;
|
||||
private readonly DispatcherTimer _responsiveLayoutTimer;
|
||||
private CancellationTokenSource? _gitStatusRefreshCts;
|
||||
private int _displayedLength; // 현재 화면에 표시된 글자 수
|
||||
private ResourceDictionary? _agentThemeDictionary;
|
||||
@@ -273,6 +274,14 @@ public partial class ChatWindow : Window
|
||||
_tokenUsagePopupCloseTimer.Stop();
|
||||
CloseTokenUsagePopupIfIdle();
|
||||
};
|
||||
_responsiveLayoutTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(90) };
|
||||
_responsiveLayoutTimer.Tick += (_, _) =>
|
||||
{
|
||||
_responsiveLayoutTimer.Stop();
|
||||
UpdateTopicPresetScrollMode();
|
||||
if (UpdateResponsiveChatLayout())
|
||||
RenderMessages(preserveViewport: true);
|
||||
};
|
||||
|
||||
KeyDown += ChatWindow_KeyDown;
|
||||
MouseMove += (_, _) =>
|
||||
@@ -401,9 +410,8 @@ public partial class ChatWindow : Window
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateTopicPresetScrollMode();
|
||||
if (UpdateResponsiveChatLayout())
|
||||
RenderMessages(preserveViewport: true);
|
||||
_responsiveLayoutTimer.Stop();
|
||||
_responsiveLayoutTimer.Start();
|
||||
};
|
||||
Closed += (_, _) =>
|
||||
{
|
||||
@@ -415,6 +423,7 @@ public partial class ChatWindow : Window
|
||||
_typingTimer.Stop();
|
||||
_conversationSearchTimer.Stop();
|
||||
_inputUiRefreshTimer.Stop();
|
||||
_responsiveLayoutTimer.Stop();
|
||||
_llm.Dispose();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -617,7 +617,7 @@ public partial class LauncherWindow : Window
|
||||
|
||||
_rainbowTimer = new System.Windows.Threading.DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(20)
|
||||
Interval = TimeSpan.FromMilliseconds(40)
|
||||
};
|
||||
var startTime = DateTime.UtcNow;
|
||||
_rainbowTimer.Tick += (_, _) =>
|
||||
|
||||
Reference in New Issue
Block a user