- 계획 승인 기본 흐름을 transcript inline 우선 구조로 더 정리하고, 계획 버튼은 저장된 계획을 여는 상세 보기 성격으로 분리했습니다. - OperationalStatusPresentationState를 확장해 runtime badge, compact strip, quick strip의 문구·강조색·노출 여부를 한 번에 계산하도록 통합했습니다. - ChatWindow 상태선/quick strip/status token 로직을 StatusPresentation partial로 분리해 메인 창 코드의 직접 분기와 렌더 책임을 줄였습니다. - 문서 이력(README, DEVELOPMENT)을 2026-04-06 01:37 KST 기준으로 갱신했습니다. - 검증: 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:
@@ -69,6 +69,8 @@ public partial class ChatWindow : Window
|
||||
private WorkflowAnalyzerWindow? _analyzerWindow; // 워크플로우 분석기
|
||||
private PlanViewerWindow? _planViewerWindow; // 실행 계획 뷰어
|
||||
private Border? _userAskCard; // transcript 내 질문 카드
|
||||
private string? _pendingPlanSummary;
|
||||
private List<string> _pendingPlanSteps = new();
|
||||
private bool _userScrolled; // 사용자가 위로 스크롤했는지
|
||||
private readonly HashSet<string> _sessionPermissionRules = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, bool> _sessionMcpEnabledOverrides = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -8965,89 +8967,6 @@ public partial class ChatWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTaskSummaryIndicators()
|
||||
{
|
||||
var hasLiveRuntimeActivity = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase)
|
||||
&& (_runningConversationCount > 0 || _appState.ActiveTasks.Count > 0);
|
||||
var status = _appState.GetOperationalStatusPresentation(_activeTab, hasLiveRuntimeActivity);
|
||||
|
||||
if (RuntimeActivityBadge != null)
|
||||
RuntimeActivityBadge.Visibility = status.ShowRuntimeBadge
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
if (RuntimeActivityLabel != null)
|
||||
RuntimeActivityLabel.Text = status.RuntimeLabel;
|
||||
|
||||
if (LastCompletedLabel != null)
|
||||
{
|
||||
LastCompletedLabel.Text = status.LastCompletedText;
|
||||
LastCompletedLabel.Visibility = status.ShowLastCompleted ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (ConversationStatusStrip != null && ConversationStatusStripLabel != null)
|
||||
{
|
||||
if (!status.ShowCompactStrip)
|
||||
{
|
||||
ConversationStatusStrip.Visibility = Visibility.Collapsed;
|
||||
ConversationStatusStripLabel.Text = "";
|
||||
}
|
||||
else if (string.Equals(status.StripKind, "permission_waiting", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ConversationStatusStrip.Visibility = Visibility.Visible;
|
||||
ConversationStatusStrip.Background = BrushFromHex("#FFF7ED");
|
||||
ConversationStatusStrip.BorderBrush = BrushFromHex("#FDBA74");
|
||||
ConversationStatusStripLabel.Foreground = BrushFromHex("#C2410C");
|
||||
ConversationStatusStripLabel.Text = status.StripText;
|
||||
}
|
||||
else if (string.Equals(status.StripKind, "failed_run", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(status.StripKind, "permission_denied", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ConversationStatusStrip.Visibility = Visibility.Visible;
|
||||
ConversationStatusStrip.Background = BrushFromHex("#FEF2F2");
|
||||
ConversationStatusStrip.BorderBrush = BrushFromHex("#FECACA");
|
||||
ConversationStatusStripLabel.Foreground = BrushFromHex("#991B1B");
|
||||
ConversationStatusStripLabel.Text = status.StripText;
|
||||
}
|
||||
else
|
||||
{
|
||||
ConversationStatusStrip.Visibility = Visibility.Collapsed;
|
||||
ConversationStatusStripLabel.Text = "";
|
||||
}
|
||||
}
|
||||
|
||||
UpdateConversationQuickStripUi();
|
||||
}
|
||||
|
||||
private void UpdateConversationQuickStripUi()
|
||||
{
|
||||
if (ConversationQuickStrip == null || QuickRunningLabel == null || QuickHotLabel == null
|
||||
|| BtnQuickRunningFilter == null || BtnQuickHotSort == null)
|
||||
return;
|
||||
|
||||
var allowQuickStrip = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase);
|
||||
var hasQuickSignal = allowQuickStrip
|
||||
&& ((_runningOnlyFilter && _runningConversationCount > 0)
|
||||
|| (!_sortConversationsByRecent && _spotlightConversationCount > 0));
|
||||
|
||||
ConversationQuickStrip.Visibility = hasQuickSignal
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
QuickRunningLabel.Text = _runningConversationCount > 0 ? $"진행 {_runningConversationCount}" : "진행";
|
||||
QuickHotLabel.Text = _spotlightConversationCount > 0 ? $"활동 {_spotlightConversationCount}" : "활동";
|
||||
|
||||
BtnQuickRunningFilter.Background = _runningOnlyFilter ? BrushFromHex("#DBEAFE") : BrushFromHex("#F8FAFC");
|
||||
BtnQuickRunningFilter.BorderBrush = _runningOnlyFilter ? BrushFromHex("#93C5FD") : BrushFromHex("#E5E7EB");
|
||||
BtnQuickRunningFilter.BorderThickness = new Thickness(1);
|
||||
QuickRunningLabel.Foreground = _runningOnlyFilter ? BrushFromHex("#1D4ED8") : (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
|
||||
|
||||
BtnQuickHotSort.Background = !_sortConversationsByRecent ? BrushFromHex("#F5F3FF") : BrushFromHex("#F8FAFC");
|
||||
BtnQuickHotSort.BorderBrush = !_sortConversationsByRecent ? BrushFromHex("#C4B5FD") : BrushFromHex("#E5E7EB");
|
||||
BtnQuickHotSort.BorderThickness = new Thickness(1);
|
||||
QuickHotLabel.Foreground = !_sortConversationsByRecent ? BrushFromHex("#6D28D9") : (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
|
||||
}
|
||||
|
||||
private static string GetRunStatusLabel(string? status)
|
||||
=> status switch
|
||||
{
|
||||
@@ -17812,53 +17731,6 @@ public partial class ChatWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStatus(string text, bool spinning)
|
||||
{
|
||||
if (StatusLabel != null) StatusLabel.Text = text;
|
||||
if (spinning) StartStatusAnimation();
|
||||
}
|
||||
|
||||
private static bool IsDecisionPending(string? summary)
|
||||
{
|
||||
var text = summary?.Trim() ?? "";
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return true;
|
||||
|
||||
return text.Contains("확인 대기", StringComparison.OrdinalIgnoreCase)
|
||||
|| text.Contains("승인 대기", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsDecisionApproved(string? summary)
|
||||
{
|
||||
var text = summary?.Trim() ?? "";
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return false;
|
||||
|
||||
return text.Contains("계획 승인", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsDecisionRejected(string? summary)
|
||||
{
|
||||
var text = summary?.Trim() ?? "";
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return false;
|
||||
|
||||
return text.Contains("계획 반려", StringComparison.OrdinalIgnoreCase)
|
||||
|| text.Contains("수정 요청", StringComparison.OrdinalIgnoreCase)
|
||||
|| text.Contains("취소", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GetDecisionStatusText(string? summary)
|
||||
{
|
||||
if (IsDecisionPending(summary))
|
||||
return "계획 승인 대기 중";
|
||||
if (IsDecisionApproved(summary))
|
||||
return "계획 승인됨 — 실행 시작";
|
||||
if (IsDecisionRejected(summary))
|
||||
return "계획 반려됨 — 계획 재작성";
|
||||
return string.IsNullOrWhiteSpace(summary) ? "사용자 의사결정 대기 중" : TruncateForStatus(summary);
|
||||
}
|
||||
|
||||
private void StartStatusAnimation()
|
||||
{
|
||||
if (_statusSpinStoryboard != null) return;
|
||||
@@ -17884,37 +17756,6 @@ public partial class ChatWindow : Window
|
||||
_statusSpinStoryboard = null;
|
||||
}
|
||||
|
||||
private void SetStatusIdle()
|
||||
{
|
||||
StopStatusAnimation();
|
||||
if (StatusLabel != null) StatusLabel.Text = "대기 중";
|
||||
if (StatusElapsed != null)
|
||||
{
|
||||
StatusElapsed.Text = "";
|
||||
StatusElapsed.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
if (StatusTokens != null)
|
||||
{
|
||||
StatusTokens.Text = "";
|
||||
StatusTokens.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
RefreshContextUsageVisual();
|
||||
ScheduleGitBranchRefresh(250);
|
||||
}
|
||||
|
||||
private void UpdateStatusTokens(int inputTokens, int outputTokens)
|
||||
{
|
||||
if (StatusTokens == null) return;
|
||||
var llm = _settings.Settings.Llm;
|
||||
var (inCost, outCost) = Services.TokenEstimator.EstimateCost(
|
||||
inputTokens, outputTokens, llm.Service, llm.Model);
|
||||
var totalCost = inCost + outCost;
|
||||
var costText = totalCost > 0 ? $" · {Services.TokenEstimator.FormatCost(totalCost)}" : "";
|
||||
StatusTokens.Text = $"↑{Services.TokenEstimator.Format(inputTokens)} ↓{Services.TokenEstimator.Format(outputTokens)}{costText}";
|
||||
StatusTokens.Visibility = Visibility.Visible;
|
||||
RefreshContextUsageVisual();
|
||||
}
|
||||
|
||||
private void BtnCompactNow_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isStreaming)
|
||||
|
||||
Reference in New Issue
Block a user