using System.Windows; using System.Windows.Input; using System.Windows.Media; namespace AxCopilot.Views; public partial class ChatWindow { private void RefreshContextUsageVisual() { if (TokenUsageCard == null || TokenUsageArc == null || TokenUsagePercentText == null || TokenUsageSummaryText == null || TokenUsageHintText == null || TokenUsageThresholdMarker == null || CompactNowLabel == null) return; var showContextUsage = _activeTab is "Cowork" or "Code"; TokenUsageCard.Visibility = showContextUsage ? Visibility.Visible : Visibility.Collapsed; if (!showContextUsage) { if (TokenUsagePopup != null) TokenUsagePopup.IsOpen = false; return; } var llm = _settings.Settings.Llm; var maxContextTokens = Math.Clamp(llm.MaxContextTokens, 1024, 1_000_000); var triggerPercent = Math.Clamp(llm.ContextCompactTriggerPercent, 10, 95); var triggerRatio = triggerPercent / 100.0; int messageTokens; lock (_convLock) messageTokens = _currentConversation?.Messages?.Count > 0 ? Services.TokenEstimator.EstimateMessages(_currentConversation.Messages) : 0; var draftText = InputBox?.Text ?? ""; var draftTokens = string.IsNullOrWhiteSpace(draftText) ? 0 : Services.TokenEstimator.Estimate(draftText) + 4; var currentTokens = Math.Max(0, messageTokens + draftTokens); var usageRatio = Services.TokenEstimator.GetContextUsage(currentTokens, maxContextTokens); var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue; Brush progressBrush = accentBrush; string summary; string compactLabel; if (usageRatio >= 1.0) { progressBrush = Brushes.IndianRed; summary = "컨텍스트 한도 초과"; compactLabel = "지금 압축"; } else if (usageRatio >= triggerRatio) { progressBrush = Brushes.DarkOrange; summary = llm.EnableProactiveContextCompact ? "곧 자동 압축" : "압축 임계 도달"; compactLabel = "압축 권장"; } else if (usageRatio >= triggerRatio * 0.7) { progressBrush = Brushes.Goldenrod; summary = "컨텍스트 사용 증가"; compactLabel = "미리 압축"; } else { summary = "컨텍스트 여유"; compactLabel = "압축"; } TokenUsageArc.Stroke = progressBrush; TokenUsageThresholdMarker.Fill = progressBrush; var percentText = $"{Math.Round(usageRatio * 100):0}%"; TokenUsagePercentText.Text = percentText; TokenUsageSummaryText.Text = $"컨텍스트 {percentText}"; TokenUsageHintText.Text = $"{Services.TokenEstimator.Format(currentTokens)} / {Services.TokenEstimator.Format(maxContextTokens)}"; CompactNowLabel.Text = compactLabel; if (TokenUsagePopupTitle != null) TokenUsagePopupTitle.Text = $"컨텍스트 창 {percentText}"; if (TokenUsagePopupUsage != null) TokenUsagePopupUsage.Text = $"{Services.TokenEstimator.Format(currentTokens)}/{Services.TokenEstimator.Format(maxContextTokens)}"; if (TokenUsagePopupDetail != null) TokenUsagePopupDetail.Text = _pendingPostCompaction ? "compact 후 첫 응답 대기 중" : $"자동 압축 시작 {triggerPercent}%"; if (TokenUsagePopupCompact != null) TokenUsagePopupCompact.Text = "AX Agent가 컨텍스트를 자동으로 관리합니다"; TokenUsageCard.ToolTip = null; UpdateCircularUsageArc(TokenUsageArc, usageRatio, 14, 14, 11); PositionThresholdMarker(TokenUsageThresholdMarker, triggerRatio, 14, 14, 11, 2.5); } private void TokenUsageCard_MouseEnter(object sender, MouseEventArgs e) { _tokenUsagePopupCloseTimer.Stop(); if (TokenUsagePopup != null && TokenUsageCard?.Visibility == Visibility.Visible) TokenUsagePopup.IsOpen = true; } private void TokenUsageCard_MouseLeave(object sender, MouseEventArgs e) { _tokenUsagePopupCloseTimer.Stop(); _tokenUsagePopupCloseTimer.Start(); } private void TokenUsagePopup_MouseEnter(object sender, MouseEventArgs e) { _tokenUsagePopupCloseTimer.Stop(); } private void TokenUsagePopup_MouseLeave(object sender, MouseEventArgs e) { _tokenUsagePopupCloseTimer.Stop(); _tokenUsagePopupCloseTimer.Start(); } private void CloseTokenUsagePopupIfIdle() { if (TokenUsagePopup == null) return; var cardHovered = IsMouseInsideElement(TokenUsageCard); var popupHovered = TokenUsagePopup.Child is FrameworkElement popupChild && IsMouseInsideElement(popupChild); if (!cardHovered && !popupHovered) TokenUsagePopup.IsOpen = false; } private static bool IsMouseInsideElement(FrameworkElement? element) { if (element == null || !element.IsVisible || element.ActualWidth <= 0 || element.ActualHeight <= 0) return false; try { var mouse = System.Windows.Forms.Control.MousePosition; var point = element.PointFromScreen(new Point(mouse.X, mouse.Y)); return point.X >= 0 && point.Y >= 0 && point.X <= element.ActualWidth && point.Y <= element.ActualHeight; } catch { return element.IsMouseOver; } } }