- 런처 색인에서 .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)
|
- 업데이트: 2026-04-06 15:31 (KST)
|
||||||
- 코워크/코드 하단 우측의 권한 요청 버튼은 footer 작업 바와 더 자연스럽게 이어지도록 외곽 테두리를 제거해 플랫한 형태로 정리했습니다.
|
- 코워크/코드 하단 우측의 권한 요청 버튼은 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)
|
- 업데이트: 2026-04-06 15:26 (KST)
|
||||||
- AX Agent 채팅/코워크/코드 하단 안내 문구를 현재 구현 기준으로 다시 정리했습니다. 입력창 워터마크는 탭 종류와 작업 폴더 선택 여부에 따라 실제 가능한 작업을 더 정확히 안내합니다.
|
- AX Agent 채팅/코워크/코드 하단 안내 문구를 현재 구현 기준으로 다시 정리했습니다. 입력창 워터마크는 탭 종류와 작업 폴더 선택 여부에 따라 실제 가능한 작업을 더 정확히 안내합니다.
|
||||||
- 선택된 프리셋 안내도 placeholder 문구 대신 실제 설명 중심으로 정리해, 프리셋 설명 카드와 입력창 워터마크가 서로 다른 역할로 보이도록 맞췄습니다.
|
- 선택된 프리셋 안내도 placeholder 문구 대신 실제 설명 중심으로 정리해, 프리셋 설명 카드와 입력창 워터마크가 서로 다른 역할로 보이도록 맞췄습니다.
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# AX Copilot - 媛쒕컻 臾몄꽌
|
# 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: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) - 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.
|
- 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>
|
/// </summary>
|
||||||
public class IndexService : IDisposable
|
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 SettingsService _settings;
|
||||||
private readonly List<FileSystemWatcher> _watchers = new();
|
private readonly List<FileSystemWatcher> _watchers = new();
|
||||||
private readonly object _timerLock = new();
|
private readonly object _timerLock = new();
|
||||||
@@ -190,6 +205,9 @@ public class IndexService : IDisposable
|
|||||||
|
|
||||||
private void OnWatcherEvent(object sender, FileSystemEventArgs e)
|
private void OnWatcherEvent(object sender, FileSystemEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (ShouldIgnorePath(e.FullPath))
|
||||||
|
return;
|
||||||
|
|
||||||
var rootPath = (sender as FileSystemWatcher)?.Path;
|
var rootPath = (sender as FileSystemWatcher)?.Path;
|
||||||
if (TryApplyIncrementalChange(e.ChangeType, e.FullPath, rootPath))
|
if (TryApplyIncrementalChange(e.ChangeType, e.FullPath, rootPath))
|
||||||
return;
|
return;
|
||||||
@@ -199,6 +217,9 @@ public class IndexService : IDisposable
|
|||||||
|
|
||||||
private void OnWatcherRenamed(object sender, RenamedEventArgs e)
|
private void OnWatcherRenamed(object sender, RenamedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (ShouldIgnorePath(e.FullPath) && ShouldIgnorePath(e.OldFullPath))
|
||||||
|
return;
|
||||||
|
|
||||||
var rootPath = (sender as FileSystemWatcher)?.Path;
|
var rootPath = (sender as FileSystemWatcher)?.Path;
|
||||||
if (TryApplyIncrementalRename(e.OldFullPath, e.FullPath, rootPath))
|
if (TryApplyIncrementalRename(e.OldFullPath, e.FullPath, rootPath))
|
||||||
return;
|
return;
|
||||||
@@ -276,6 +297,9 @@ public class IndexService : IDisposable
|
|||||||
|
|
||||||
private bool AddPathEntryIncrementally(string fullPath, string rootPath)
|
private bool AddPathEntryIncrementally(string fullPath, string rootPath)
|
||||||
{
|
{
|
||||||
|
if (ShouldIgnorePath(fullPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
var updated = false;
|
var updated = false;
|
||||||
|
|
||||||
lock (_indexLock)
|
lock (_indexLock)
|
||||||
@@ -307,6 +331,9 @@ public class IndexService : IDisposable
|
|||||||
|
|
||||||
private bool RemovePathEntriesIncrementally(string fullPath)
|
private bool RemovePathEntriesIncrementally(string fullPath)
|
||||||
{
|
{
|
||||||
|
if (ShouldIgnorePath(fullPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
var normalizedPath = NormalizePath(fullPath);
|
var normalizedPath = NormalizePath(fullPath);
|
||||||
lock (_indexLock)
|
lock (_indexLock)
|
||||||
{
|
{
|
||||||
@@ -340,6 +367,9 @@ public class IndexService : IDisposable
|
|||||||
|
|
||||||
private void ScheduleRebuild(string triggerPath)
|
private void ScheduleRebuild(string triggerPath)
|
||||||
{
|
{
|
||||||
|
if (ShouldIgnorePath(triggerPath))
|
||||||
|
return;
|
||||||
|
|
||||||
LogService.Info($"파일 변경 감지: {triggerPath} — {RebuildDebounceMs}ms 후 전체 재빌드 예약");
|
LogService.Info($"파일 변경 감지: {triggerPath} — {RebuildDebounceMs}ms 후 전체 재빌드 예약");
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
@@ -686,7 +716,7 @@ public class IndexService : IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var count = 0;
|
var count = 0;
|
||||||
foreach (var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
|
foreach (var file in EnumerateFilesWithIgnoredDirectories(dir))
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
@@ -704,7 +734,7 @@ public class IndexService : IDisposable
|
|||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
var name = Path.GetFileName(subDir);
|
var name = Path.GetFileName(subDir);
|
||||||
if (name.StartsWith(".", StringComparison.Ordinal))
|
if (name.StartsWith(".", StringComparison.Ordinal) || IgnoredDirectoryNames.Contains(name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
entries.Add(CreateFolderEntry(subDir));
|
entries.Add(CreateFolderEntry(subDir));
|
||||||
@@ -717,6 +747,78 @@ public class IndexService : IDisposable
|
|||||||
}, ct);
|
}, 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)
|
private static IndexEntry CreateFileEntry(string filePath)
|
||||||
{
|
{
|
||||||
var ext = Path.GetExtension(filePath).ToLowerInvariant();
|
var ext = Path.GetExtension(filePath).ToLowerInvariant();
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ public partial class ChatWindow : Window
|
|||||||
private readonly DispatcherTimer _conversationPersistTimer;
|
private readonly DispatcherTimer _conversationPersistTimer;
|
||||||
private readonly DispatcherTimer _agentUiEventTimer;
|
private readonly DispatcherTimer _agentUiEventTimer;
|
||||||
private readonly DispatcherTimer _tokenUsagePopupCloseTimer;
|
private readonly DispatcherTimer _tokenUsagePopupCloseTimer;
|
||||||
|
private readonly DispatcherTimer _responsiveLayoutTimer;
|
||||||
private CancellationTokenSource? _gitStatusRefreshCts;
|
private CancellationTokenSource? _gitStatusRefreshCts;
|
||||||
private int _displayedLength; // 현재 화면에 표시된 글자 수
|
private int _displayedLength; // 현재 화면에 표시된 글자 수
|
||||||
private ResourceDictionary? _agentThemeDictionary;
|
private ResourceDictionary? _agentThemeDictionary;
|
||||||
@@ -273,6 +274,14 @@ public partial class ChatWindow : Window
|
|||||||
_tokenUsagePopupCloseTimer.Stop();
|
_tokenUsagePopupCloseTimer.Stop();
|
||||||
CloseTokenUsagePopupIfIdle();
|
CloseTokenUsagePopupIfIdle();
|
||||||
};
|
};
|
||||||
|
_responsiveLayoutTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(90) };
|
||||||
|
_responsiveLayoutTimer.Tick += (_, _) =>
|
||||||
|
{
|
||||||
|
_responsiveLayoutTimer.Stop();
|
||||||
|
UpdateTopicPresetScrollMode();
|
||||||
|
if (UpdateResponsiveChatLayout())
|
||||||
|
RenderMessages(preserveViewport: true);
|
||||||
|
};
|
||||||
|
|
||||||
KeyDown += ChatWindow_KeyDown;
|
KeyDown += ChatWindow_KeyDown;
|
||||||
MouseMove += (_, _) =>
|
MouseMove += (_, _) =>
|
||||||
@@ -401,9 +410,8 @@ public partial class ChatWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateTopicPresetScrollMode();
|
_responsiveLayoutTimer.Stop();
|
||||||
if (UpdateResponsiveChatLayout())
|
_responsiveLayoutTimer.Start();
|
||||||
RenderMessages(preserveViewport: true);
|
|
||||||
};
|
};
|
||||||
Closed += (_, _) =>
|
Closed += (_, _) =>
|
||||||
{
|
{
|
||||||
@@ -415,6 +423,7 @@ public partial class ChatWindow : Window
|
|||||||
_typingTimer.Stop();
|
_typingTimer.Stop();
|
||||||
_conversationSearchTimer.Stop();
|
_conversationSearchTimer.Stop();
|
||||||
_inputUiRefreshTimer.Stop();
|
_inputUiRefreshTimer.Stop();
|
||||||
|
_responsiveLayoutTimer.Stop();
|
||||||
_llm.Dispose();
|
_llm.Dispose();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -617,7 +617,7 @@ public partial class LauncherWindow : Window
|
|||||||
|
|
||||||
_rainbowTimer = new System.Windows.Threading.DispatcherTimer
|
_rainbowTimer = new System.Windows.Threading.DispatcherTimer
|
||||||
{
|
{
|
||||||
Interval = TimeSpan.FromMilliseconds(20)
|
Interval = TimeSpan.FromMilliseconds(40)
|
||||||
};
|
};
|
||||||
var startTime = DateTime.UtcNow;
|
var startTime = DateTime.UtcNow;
|
||||||
_rainbowTimer.Tick += (_, _) =>
|
_rainbowTimer.Tick += (_, _) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user