- claude-code 선택적 탐색 흐름을 참고해 Cowork/Code 시스템 프롬프트에서 folder_map 상시 선행 지시를 완화하고 glob/grep 기반 좁은 탐색을 우선하도록 조정함 - FolderMapTool 기본 depth를 2로, include_files 기본값을 false로 낮추고 MultiReadTool 최대 파일 수를 8개로 줄여 초기 과탐색 폭을 보수적으로 조정함 - AgentLoopExplorationPolicy partial을 추가해 탐색 범위 분류, broad-scan corrective hint, exploration_breadth 성능 로그를 연결함 - AgentLoopService에 탐색 범위 가이드 주입과 실행 중 탐색 폭 추적을 추가하고, 좁은 질문에서 반복적인 folder_map/대량 multi_read를 교정하도록 정리함 - DocxToHtmlConverter nullable 경고를 수정해 Release 빌드 경고 0 / 오류 0 기준을 다시 충족함 - README와 docs/DEVELOPMENT.md에 2026-04-09 10:36 (KST) 기준 개발 이력을 반영함
This commit is contained in:
@@ -251,7 +251,7 @@ public partial class ChatWindow : Window
|
||||
_gitRefreshTimer.Stop();
|
||||
await RefreshGitBranchStatusAsync();
|
||||
};
|
||||
_conversationSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(140) };
|
||||
_conversationSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) };
|
||||
_conversationSearchTimer.Tick += (_, _) =>
|
||||
{
|
||||
_conversationSearchTimer.Stop();
|
||||
@@ -301,15 +301,15 @@ public partial class ChatWindow : Window
|
||||
_conversationPersistTimer.Stop();
|
||||
FlushPendingConversationPersists();
|
||||
};
|
||||
_agentUiEventTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(140) };
|
||||
_agentUiEventTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
|
||||
_agentUiEventTimer.Tick += (_, _) =>
|
||||
{
|
||||
_agentUiEventTimer.Stop();
|
||||
_agentUiEventTimer.Interval = _isStreaming
|
||||
? (IsLightweightLiveProgressMode()
|
||||
? TimeSpan.FromMilliseconds(420)
|
||||
: TimeSpan.FromMilliseconds(300))
|
||||
: TimeSpan.FromMilliseconds(140);
|
||||
? TimeSpan.FromMilliseconds(500)
|
||||
: TimeSpan.FromMilliseconds(350))
|
||||
: TimeSpan.FromMilliseconds(200);
|
||||
FlushPendingAgentUiEvent();
|
||||
};
|
||||
_agentProgressHintTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
@@ -320,7 +320,7 @@ public partial class ChatWindow : Window
|
||||
_tokenUsagePopupCloseTimer.Stop();
|
||||
CloseTokenUsagePopupIfIdle();
|
||||
};
|
||||
_responsiveLayoutTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(120) };
|
||||
_responsiveLayoutTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
|
||||
_responsiveLayoutTimer.Tick += (_, _) =>
|
||||
{
|
||||
_responsiveLayoutTimer.Stop();
|
||||
@@ -379,11 +379,17 @@ public partial class ChatWindow : Window
|
||||
UpdateInputBoxHeight();
|
||||
InputBox.Focus();
|
||||
// ── 무거운 작업은 유휴 시점에 비동기 실행 ──
|
||||
// A-1: 패널 이벤트 위임 1회 초기화 — 개별 람다 대신 부모 레벨에서 처리
|
||||
InitConversationPanelDelegation();
|
||||
InitTopicPanelDelegation();
|
||||
InitPreviewSplitButtonHover();
|
||||
InitPlanButtonHover();
|
||||
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
TemplateService.LoadCustomMoods(_settings.Settings.Llm.CustomMoods);
|
||||
BuildTopicButtons();
|
||||
RestoreLastConversations();
|
||||
BuildTopicButtons();
|
||||
RefreshConversationList();
|
||||
UpdateResponsiveChatLayout();
|
||||
UpdateTaskSummaryIndicators();
|
||||
@@ -471,13 +477,27 @@ public partial class ChatWindow : Window
|
||||
_settings.SettingsChanged -= Settings_SettingsChanged;
|
||||
SubAgentTool.StatusChanged -= OnSubAgentStatusChanged;
|
||||
foreach (var cts in _tabStreamCts.Values) cts.Cancel();
|
||||
|
||||
// 모든 DispatcherTimer 명시적 Stop — auto-stop에만 의존하지 않음
|
||||
_cursorTimer.Stop();
|
||||
_elapsedTimer.Stop();
|
||||
_typingTimer.Stop();
|
||||
_conversationSearchTimer.Stop();
|
||||
_inputUiRefreshTimer.Stop();
|
||||
_responsiveLayoutTimer.Stop();
|
||||
_gitRefreshTimer.Stop();
|
||||
_executionHistoryRenderTimer.Stop();
|
||||
_taskSummaryRefreshTimer.Stop();
|
||||
_conversationPersistTimer.Stop();
|
||||
_agentUiEventTimer.Stop();
|
||||
_agentProgressHintTimer.Stop();
|
||||
_tokenUsagePopupCloseTimer.Stop();
|
||||
_smoothScrollTimer?.Stop();
|
||||
_sidebarAnimTimer?.Stop();
|
||||
_fileBrowserRefreshTimer?.Stop();
|
||||
|
||||
StopRainbowGlow();
|
||||
StopAgentEventProcessor();
|
||||
_llm.Dispose();
|
||||
};
|
||||
}
|
||||
@@ -688,25 +708,18 @@ public partial class ChatWindow : Window
|
||||
animation.Completed += (_, _) => ScrollTranscriptToVerticalOffset(targetOffset);
|
||||
|
||||
// ScrollViewer에 직접 애니메이션을 적용할 수 없으므로 타이머 기반으로 보간
|
||||
// 기존 스크롤 애니메이션이 진행 중이면 즉시 종료
|
||||
_smoothScrollTimer?.Stop();
|
||||
var startTime = DateTime.UtcNow;
|
||||
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) }; // ~60fps
|
||||
EventHandler tickHandler = null!;
|
||||
tickHandler = (_, _) =>
|
||||
_smoothScrollStartOffset = currentOffset;
|
||||
_smoothScrollDiff = diff;
|
||||
_smoothScrollStartTime = startTime;
|
||||
if (_smoothScrollTimer == null)
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
var progress = Math.Min(elapsed / 200.0, 1.0);
|
||||
var eased = 1.0 - Math.Pow(1.0 - progress, 3);
|
||||
var offset = currentOffset + diff * eased;
|
||||
ScrollTranscriptToVerticalOffset(offset);
|
||||
|
||||
if (progress >= 1.0)
|
||||
{
|
||||
timer.Stop();
|
||||
timer.Tick -= tickHandler;
|
||||
}
|
||||
};
|
||||
timer.Tick += tickHandler;
|
||||
timer.Start();
|
||||
_smoothScrollTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(32) }; // ~30fps (충분히 부드러움)
|
||||
_smoothScrollTimer.Tick += SmoothScrollTimer_Tick;
|
||||
}
|
||||
_smoothScrollTimer.Start();
|
||||
}
|
||||
|
||||
// ─── 대화 제목 인라인 편집 ──────────────────────────────────────────
|
||||
@@ -1311,6 +1324,16 @@ public partial class ChatWindow : Window
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
if (!isOwningTab) PauseIcon.Text = "\uE769";
|
||||
|
||||
// 스트리밍 중인 탭이 아니면 펄스 닷·상태 바 숨김 (다른 탭 작업 상태가 보이지 않도록)
|
||||
// 스트리밍 탭으로 복귀 시 자동 복원
|
||||
if (PulseDotBar != null)
|
||||
{
|
||||
if (!isOwningTab)
|
||||
PulseDotBar.Visibility = Visibility.Collapsed;
|
||||
else if (_streamingTabs.Count > 0)
|
||||
PulseDotBar.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void TabChat_Checked(object sender, RoutedEventArgs e)
|
||||
@@ -2223,24 +2246,18 @@ public partial class ChatWindow : Window
|
||||
{
|
||||
var duration = 200.0;
|
||||
var start = DateTime.UtcNow;
|
||||
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(10) };
|
||||
EventHandler tickHandler = null!;
|
||||
tickHandler = (_, _) =>
|
||||
_sidebarAnimTimer?.Stop();
|
||||
_sidebarAnimFrom = from;
|
||||
_sidebarAnimTo = to;
|
||||
_sidebarAnimStart = start;
|
||||
_sidebarAnimDuration = duration;
|
||||
_sidebarAnimComplete = onComplete;
|
||||
if (_sidebarAnimTimer == null)
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - start).TotalMilliseconds;
|
||||
var t = Math.Min(elapsed / duration, 1.0);
|
||||
t = 1 - (1 - t) * (1 - t);
|
||||
SidebarColumn.Width = new GridLength(from + (to - from) * t);
|
||||
if (elapsed >= duration)
|
||||
{
|
||||
timer.Stop();
|
||||
timer.Tick -= tickHandler;
|
||||
SidebarColumn.Width = new GridLength(to);
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
};
|
||||
timer.Tick += tickHandler;
|
||||
timer.Start();
|
||||
_sidebarAnimTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(32) };
|
||||
_sidebarAnimTimer.Tick += SidebarAnimTimer_Tick;
|
||||
}
|
||||
_sidebarAnimTimer.Start();
|
||||
}
|
||||
|
||||
// ─── 대화 목록 ────────────────────────────────────────────────────────
|
||||
@@ -2254,6 +2271,27 @@ public partial class ChatWindow : Window
|
||||
_conversationSearchTimer.Start();
|
||||
}
|
||||
|
||||
// ─── 사이드바 애니메이션 (재사용 타이머) ──────────────────────────────
|
||||
|
||||
private DispatcherTimer? _sidebarAnimTimer;
|
||||
private double _sidebarAnimFrom, _sidebarAnimTo, _sidebarAnimDuration;
|
||||
private DateTime _sidebarAnimStart;
|
||||
private Action? _sidebarAnimComplete;
|
||||
|
||||
private void SidebarAnimTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - _sidebarAnimStart).TotalMilliseconds;
|
||||
var t = Math.Min(elapsed / _sidebarAnimDuration, 1.0);
|
||||
t = 1 - (1 - t) * (1 - t);
|
||||
SidebarColumn.Width = new GridLength(_sidebarAnimFrom + (_sidebarAnimTo - _sidebarAnimFrom) * t);
|
||||
if (elapsed >= _sidebarAnimDuration)
|
||||
{
|
||||
_sidebarAnimTimer?.Stop();
|
||||
SidebarColumn.Width = new GridLength(_sidebarAnimTo);
|
||||
_sidebarAnimComplete?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatDate(DateTime dt)
|
||||
{
|
||||
var diff = DateTime.Now - dt;
|
||||
@@ -5805,6 +5843,12 @@ public partial class ChatWindow : Window
|
||||
{
|
||||
await ShowTypedAssistantPreviewAsync(assistantContent, streamToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 에이전트 루프 완료 시 assistantContent가 비어 있으면
|
||||
// ShowTypedAssistantPreviewAsync가 호출되지 않아 라이브 카드가 남음 → 명시적 제거
|
||||
RemoveAgentLiveCard();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -6034,9 +6078,6 @@ public partial class ChatWindow : Window
|
||||
PreviewWindow.RefreshIfOpen(evt.FilePath);
|
||||
else
|
||||
TryShowPreview(evt.FilePath);
|
||||
|
||||
if (!PreviewWindow.IsOpen)
|
||||
TryShowPreview(evt.FilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6197,11 +6238,15 @@ public partial class ChatWindow : Window
|
||||
{
|
||||
case "active":
|
||||
sb.AppendLine("IMPORTANT: Folder Data Usage = ACTIVE. You have 'document_read' and 'folder_map' tools available.");
|
||||
sb.AppendLine("Before creating reports, use folder_map to scan the work folder structure. " +
|
||||
"Then EVALUATE whether each document is RELEVANT to the user's current request topic. " +
|
||||
"Only use document_read on files that are clearly related to the conversation subject. " +
|
||||
"Do NOT read or reference files that are unrelated to the user's request, even if they exist in the folder. " +
|
||||
"In your planning step, list which files you plan to read and explain WHY they are relevant.");
|
||||
sb.AppendLine("Use selective exploration. Start with glob or grep when the request already has a clear topic.");
|
||||
sb.AppendLine("Use folder_map only when the workspace structure is unclear or the request is repo-wide/open-ended.");
|
||||
sb.AppendLine("[CRITICAL] FILE SELECTION STRATEGY — DO NOT READ ALL FILES:");
|
||||
sb.AppendLine(" 1. Identify candidate files by filename or topic keywords first.");
|
||||
sb.AppendLine(" 2. Read ONLY files that clearly match the user's topic. Skip unrelated topics.");
|
||||
sb.AppendLine(" 3. Maximum 2-3 relevant files for the first pass. Expand only when evidence shows more files are needed.");
|
||||
sb.AppendLine(" 4. Do NOT read every file 'just in case'. Broad reading without evidence is forbidden.");
|
||||
sb.AppendLine(" 5. If no files match the topic, proceed WITHOUT reading any workspace files.");
|
||||
sb.AppendLine("VIOLATION: Reading all files in the folder is FORBIDDEN. It wastes tokens and degrades quality.");
|
||||
break;
|
||||
case "passive":
|
||||
sb.AppendLine("Folder Data Usage = PASSIVE. You have 'document_read' and 'folder_map' tools. " +
|
||||
@@ -6297,11 +6342,14 @@ public partial class ChatWindow : Window
|
||||
sb.AppendLine("IMPORTANT: When creating documents with dates, always use today's actual date above.");
|
||||
|
||||
sb.AppendLine("\n## Core Workflow (MANDATORY — follow this order)");
|
||||
sb.AppendLine("1. ORIENT: Run folder_map (depth=2) to understand project structure. Check .gitignore, README, config files.");
|
||||
sb.AppendLine("1. ORIENT: Choose the smallest exploration that matches the request.");
|
||||
sb.AppendLine(" - If the request is file-specific, bug-specific, symbol-specific, or topic-specific: start with grep/glob and targeted file_read.");
|
||||
sb.AppendLine(" - Only run folder_map (depth=2) when the repository structure is genuinely unclear or the request is repo-wide/open-ended.");
|
||||
sb.AppendLine("2. BASELINE: If tests exist, run build_run action='test' FIRST to establish baseline. Record pass/fail count.");
|
||||
sb.AppendLine("3. ANALYZE: Use grep (with context_lines=2) + file_read to deeply understand the code you'll modify.");
|
||||
sb.AppendLine("3. ANALYZE: Use grep (with context_lines=2) + targeted file_read to deeply understand the code you'll modify.");
|
||||
sb.AppendLine(" - Always check callers/references: grep for function/class names to find all usage points.");
|
||||
sb.AppendLine(" - Read test files related to the code you're changing to understand expected behavior.");
|
||||
sb.AppendLine(" - Keep the first reading pass narrow (usually 1-5 files) unless evidence shows the scope is larger.");
|
||||
sb.AppendLine("4. PLAN: Build an internal execution outline and impact assessment before editing.");
|
||||
sb.AppendLine(" - Present the outline explicitly only when the user asks for a plan or the change is clearly high risk.");
|
||||
sb.AppendLine(" - Explain WHY each change is needed and what could break.");
|
||||
@@ -6378,8 +6426,10 @@ public partial class ChatWindow : Window
|
||||
sb.Append(BuildSubAgentDelegationSection(true));
|
||||
|
||||
// 폴더 데이터 활용
|
||||
sb.AppendLine("\nFolder Data Usage = ACTIVE. Use folder_map and file_read to understand the codebase.");
|
||||
sb.AppendLine("Analyze project structure before making changes. Read relevant files to understand context.");
|
||||
sb.AppendLine("\nFolder Data Usage = ACTIVE.");
|
||||
sb.AppendLine("Prefer grep/glob + targeted file_read for narrow requests.");
|
||||
sb.AppendLine("Use folder_map only when structure is unclear or the request is repo-wide.");
|
||||
sb.AppendLine("Read only files that are relevant to the current question. Avoid broad codebase sweeps without evidence.");
|
||||
|
||||
// 프리셋 시스템 프롬프트
|
||||
lock (_convLock)
|
||||
@@ -8846,6 +8896,15 @@ public partial class ChatWindow : Window
|
||||
else
|
||||
foreach (var cts in _tabStreamCts.Values) cts.Cancel();
|
||||
|
||||
// 대기열 정리: 실행 중 + 대기 중 항목 모두 제거 (중지는 "전부 멈춤"을 의미)
|
||||
lock (_convLock)
|
||||
{
|
||||
_draftQueueProcessor.CancelRunning(ChatSession, _activeTab, _storage);
|
||||
_draftQueueProcessor.ClearQueued(ChatSession, _activeTab, _storage);
|
||||
_runningDraftId = null;
|
||||
}
|
||||
RefreshDraftQueueUi();
|
||||
|
||||
// 즉시 UI 상태 정리 — 에이전트 루프의 finally가 비동기로 도달할 때까지 대기하지 않음
|
||||
StopLiveAgentProgressHints();
|
||||
RemoveAgentLiveCard();
|
||||
@@ -9491,6 +9550,24 @@ public partial class ChatWindow : Window
|
||||
return "";
|
||||
}
|
||||
|
||||
// ─── 부드러운 스크롤 애니메이션 (재사용 타이머) ──────────────────────
|
||||
|
||||
private DispatcherTimer? _smoothScrollTimer;
|
||||
private double _smoothScrollStartOffset;
|
||||
private double _smoothScrollDiff;
|
||||
private DateTime _smoothScrollStartTime;
|
||||
|
||||
private void SmoothScrollTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - _smoothScrollStartTime).TotalMilliseconds;
|
||||
var progress = Math.Min(elapsed / 200.0, 1.0);
|
||||
var eased = 1.0 - Math.Pow(1.0 - progress, 3);
|
||||
ScrollTranscriptToVerticalOffset(_smoothScrollStartOffset + _smoothScrollDiff * eased);
|
||||
|
||||
if (progress >= 1.0)
|
||||
_smoothScrollTimer?.Stop();
|
||||
}
|
||||
|
||||
// ─── 무지개 글로우 애니메이션 ─────────────────────────────────────────
|
||||
|
||||
private DispatcherTimer? _rainbowTimer;
|
||||
@@ -11897,9 +11974,75 @@ public partial class ChatWindow : Window
|
||||
|
||||
private void OverlayOpenAuditLogBtn_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 클릭 효과 리셋
|
||||
if (sender is Border border)
|
||||
{
|
||||
border.Opacity = 1.0;
|
||||
border.RenderTransform = null;
|
||||
}
|
||||
try { System.Diagnostics.Process.Start("explorer.exe", Services.AuditLogService.GetAuditFolder()); } catch { }
|
||||
}
|
||||
|
||||
private void BtnBrowsePdfExportPath_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 클릭 효과 리셋
|
||||
if (sender is Border border)
|
||||
{
|
||||
border.Opacity = 1.0;
|
||||
border.RenderTransform = null;
|
||||
}
|
||||
|
||||
var dlg = new System.Windows.Forms.FolderBrowserDialog
|
||||
{
|
||||
Description = "PDF 내보내기 기본 폴더를 선택하세요",
|
||||
ShowNewFolderButton = true,
|
||||
UseDescriptionForTitle = true,
|
||||
};
|
||||
|
||||
var current = _settings.Settings.Llm.PdfExportPath;
|
||||
if (!string.IsNullOrWhiteSpace(current) && Directory.Exists(current))
|
||||
dlg.SelectedPath = current;
|
||||
|
||||
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK)
|
||||
return;
|
||||
|
||||
_settings.Settings.Llm.PdfExportPath = dlg.SelectedPath;
|
||||
if (TxtOverlayPdfExportPath != null)
|
||||
TxtOverlayPdfExportPath.Text = dlg.SelectedPath;
|
||||
PersistOverlaySettingsState(refreshOverlayDeferredInputs: false);
|
||||
}
|
||||
|
||||
/// <summary>설정 오버레이 액션 버튼 공통 호버/클릭 효과.</summary>
|
||||
private void OverlayActionBtn_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (sender is Border border)
|
||||
{
|
||||
border.Opacity = 0.85;
|
||||
border.Background = TryFindResource("ItemActiveBackground") as Brush
|
||||
?? TryFindResource("ItemHoverBackground") as Brush
|
||||
?? border.Background;
|
||||
}
|
||||
}
|
||||
|
||||
private void OverlayActionBtn_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (sender is Border border)
|
||||
{
|
||||
border.Opacity = 1.0;
|
||||
border.Background = TryFindResource("ItemHoverBackground") as Brush ?? border.Background;
|
||||
}
|
||||
}
|
||||
|
||||
private void OverlayActionBtn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Border border)
|
||||
{
|
||||
border.Opacity = 0.65;
|
||||
border.RenderTransform = new ScaleTransform(0.96, 0.96);
|
||||
border.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
private void ChkOverlayEnableDragDropAiActions_Changed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isOverlaySettingsSyncing || ChkOverlayEnableDragDropAiActions == null)
|
||||
@@ -14222,15 +14365,21 @@ public partial class ChatWindow : Window
|
||||
|
||||
private void BtnDeleteAll_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var tabLabel = _activeTab switch
|
||||
{
|
||||
"Cowork" => "코워크",
|
||||
"Code" => "코드",
|
||||
_ => "채팅"
|
||||
};
|
||||
var result = CustomMessageBox.Show(
|
||||
"저장된 모든 대화 내역을 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.",
|
||||
"대화 전체 삭제",
|
||||
$"'{tabLabel}' 탭의 모든 대화 내역을 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.",
|
||||
$"{tabLabel} 대화 전체 삭제",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning);
|
||||
|
||||
if (result != MessageBoxResult.Yes) return;
|
||||
|
||||
_storage.DeleteAll();
|
||||
_storage.DeleteAllByTab(_activeTab);
|
||||
lock (_convLock)
|
||||
{
|
||||
ChatSession?.ClearCurrentConversation(_activeTab);
|
||||
@@ -14377,13 +14526,20 @@ public partial class ChatWindow : Window
|
||||
return;
|
||||
}
|
||||
|
||||
await RefreshGitBranchStatusAsync();
|
||||
_gitBranchSearchText = "";
|
||||
if (GitBranchSearchBox != null)
|
||||
GitBranchSearchBox.Text = "";
|
||||
BuildGitBranchPopup();
|
||||
GitBranchPopup.IsOpen = true;
|
||||
GitBranchSearchBox?.Focus();
|
||||
try
|
||||
{
|
||||
await RefreshGitBranchStatusAsync();
|
||||
_gitBranchSearchText = "";
|
||||
if (GitBranchSearchBox != null)
|
||||
GitBranchSearchBox.Text = "";
|
||||
BuildGitBranchPopup();
|
||||
GitBranchPopup.IsOpen = true;
|
||||
GitBranchSearchBox?.Focus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Services.LogService.Error($"Git branch refresh failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void GitBranchSearchBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
@@ -14463,9 +14619,17 @@ public partial class ChatWindow : Window
|
||||
return value.ToString("N0");
|
||||
}
|
||||
|
||||
private static void UpdateCircularUsageArc(System.Windows.Shapes.Path path, double ratio, double centerX, double centerY, double radius)
|
||||
private double _lastArcRatio = -1;
|
||||
|
||||
private void UpdateCircularUsageArc(System.Windows.Shapes.Path path, double ratio, double centerX, double centerY, double radius)
|
||||
{
|
||||
ratio = Math.Clamp(ratio, 0, 0.9999);
|
||||
|
||||
// 비율 변화가 1% 미만이면 렌더링 생략 (250ms마다 호출되므로 불필요한 재생성 방지)
|
||||
if (Math.Abs(ratio - _lastArcRatio) < 0.01)
|
||||
return;
|
||||
_lastArcRatio = ratio;
|
||||
|
||||
if (ratio <= 0)
|
||||
{
|
||||
path.Data = Geometry.Empty;
|
||||
|
||||
Reference in New Issue
Block a user