diff --git a/README.md b/README.md index 8dd956e..cfd76cf 100644 --- a/README.md +++ b/README.md @@ -1448,3 +1448,7 @@ MIT License - 업데이트: 2026-04-07 02:11 (KST) - AX Agent 내부 설정 공통 탭의 Gemini/Claude API 키 입력 필드를 PasswordBox에서 TextBox로 교체해, 오버레이 동기화 중에도 입력이 끊기거나 튕기지 않도록 수정했습니다. +- 업데이트: 2026-04-07 02:45 (KST) + - Cowork/Code 진행 카드의 경과 시간 계산을 보정했습니다. 스트리밍 시작 시각이 준비되기 전에 진행 힌트가 먼저 그려질 때 `수천만 시간`처럼 비정상값이 표시되던 문제를 막고, 6시간을 넘는 비현실적인 경과 시간은 자동 무시하도록 정리했습니다. + - AX Agent 입력창 글로우를 런처와 같은 리듬의 무지개 글로우로 다시 맞췄습니다. 글로우 외곽선 두께와 블러를 부드럽게 조정하고, 라이브 진행 카드도 테마 AccentColor 기반의 은은한 톤을 써서 주황색 고정 느낌을 줄였습니다. + - 일반 설정에 있던 `런처 무지개 글로우`, `선택 아이템 글로우`, `채팅 입력창 무지개 글로우`를 AX Agent 내부 설정으로 이동해, 이제 내부 설정에서 바로 런처/입력창 글로우를 함께 조정할 수 있습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 9c41129..dfe3d6a 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5287,3 +5287,18 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - Document update: 2026-04-07 02:23 (KST) - Reconnected the AX Agent direct-chat execution path to the existing SSE/streaming transport in `LlmService`. Chat replies no longer wait for the final full string before rendering; when streaming is enabled they now advance through the existing streaming container and typing-timer path. - Document update: 2026-04-07 02:23 (KST) - Updated `AxAgentExecutionEngine.ResolveExecutionMode()` so non-agent chat can opt into streaming transport, and wired `ChatWindow.ExecutePreparedTurnAsync()` to consume `LlmService.StreamAsync(...)` for direct conversations while keeping Cowork/Code on the agent-loop progress-feed path. - Document update: 2026-04-07 02:31 (KST) - Added a typed final-response preview for Cowork/Code agent-loop completions. Those tabs still use progress-feed execution during the loop, but once a final assistant answer is available it now passes through the same streaming container/typing presentation before the final transcript message is committed. + +## 2026-04-07 02:45 (KST) + +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) + - Cowork/Code 실행 시작 시 `_streamStartTime`을 라이브 진행 힌트보다 먼저 설정하도록 순서를 조정했다. + - 유효한 시작 시각이 없을 때는 진행 힌트 경과 시간을 계산하지 않도록 막아 `수천만 시간` 같은 비정상 표시를 방지했다. + - 내부 설정 저장 시 `EnableChatRainbowGlow`, `Launcher.EnableRainbowGlow`, `Launcher.EnableSelectionGlow`도 함께 반영되도록 연결했다. +- [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs) + - 진행 카드 메타에 쓰는 경과 시간을 6시간 상한으로 정규화해 비정상값을 자동 무시하도록 했다. + - 라이브 대기 카드와 진행 줄 배경을 고정 주황색 대신 테마 AccentColor 기반 반투명 톤으로 교체했다. +- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) + - 입력창 글로우 외곽선의 두께와 블러를 완화해 런처 글로우와 더 비슷한 질감으로 조정했다. + - AX Agent 내부 설정 공통 탭에 `글로우 효과` 섹션을 추가해 런처 무지개 글로우, 런처 선택 글로우, 채팅 입력창 글로우를 내부 설정에서 바로 조정할 수 있게 했다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) + - 일반 설정 테마 섹션에 있던 런처/채팅 글로우 토글 3종을 제거해 AX Agent 내부 설정으로 UI를 일원화했다. diff --git a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs index 5b852fd..5ecf0d2 100644 --- a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs +++ b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs @@ -10,6 +10,22 @@ namespace AxCopilot.Views; public partial class ChatWindow { + private static Color ResolveLiveProgressAccentColor(Brush accentBrush) + { + return accentBrush is SolidColorBrush solid + ? solid.Color + : Color.FromRgb(0x59, 0xA5, 0xF5); + } + + private static long NormalizeProgressElapsedMs(long elapsedMs) + { + if (elapsedMs <= 0) + return 0; + + var maxReasonableElapsed = (long)TimeSpan.FromHours(6).TotalMilliseconds; + return elapsedMs > maxReasonableElapsed ? 0 : elapsedMs; + } + private Border CreateCompactEventPill( string summary, Brush primaryText, @@ -20,13 +36,14 @@ public partial class ChatWindow string? metaText = null, bool liveWaitingStyle = false) { + var liveAccentColor = ResolveLiveProgressAccentColor(accentBrush); return new Border { Background = liveWaitingStyle - ? new SolidColorBrush(Color.FromArgb(0x20, 0xF5, 0x73, 0x16)) + ? new SolidColorBrush(Color.FromArgb(0x16, liveAccentColor.R, liveAccentColor.G, liveAccentColor.B)) : hintBg, BorderBrush = liveWaitingStyle - ? new SolidColorBrush(Color.FromArgb(0x4D, 0xF5, 0x73, 0x16)) + ? new SolidColorBrush(Color.FromArgb(0x46, liveAccentColor.R, liveAccentColor.G, liveAccentColor.B)) : borderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(10), @@ -267,9 +284,10 @@ public partial class ChatWindow { var parts = new List(); - if (evt.ElapsedMs > 0) + var normalizedElapsedMs = NormalizeProgressElapsedMs(evt.ElapsedMs); + if (normalizedElapsedMs > 0) { - var elapsed = TimeSpan.FromMilliseconds(evt.ElapsedMs); + var elapsed = TimeSpan.FromMilliseconds(normalizedElapsedMs); if (elapsed.TotalHours >= 1) parts.Add($"{(int)elapsed.TotalHours}h {elapsed.Minutes}m"); else if (elapsed.TotalMinutes >= 1) @@ -324,11 +342,12 @@ public partial class ChatWindow bool liveWaitingStyle, out Border pulseMarker) { + var liveAccentColor = ResolveLiveProgressAccentColor(accentBrush); var cardBackground = liveWaitingStyle - ? new SolidColorBrush(Color.FromArgb(0x20, 0xF5, 0x73, 0x16)) + ? new SolidColorBrush(Color.FromArgb(0x16, liveAccentColor.R, liveAccentColor.G, liveAccentColor.B)) : hintBg; var cardBorder = liveWaitingStyle - ? new SolidColorBrush(Color.FromArgb(0x4D, 0xF5, 0x73, 0x16)) + ? new SolidColorBrush(Color.FromArgb(0x46, liveAccentColor.R, liveAccentColor.G, liveAccentColor.B)) : borderBrush; pulseMarker = new Border @@ -389,9 +408,10 @@ public partial class ChatWindow { var parts = new List(); - if (evt.ElapsedMs > 0) + var normalizedElapsedMs = NormalizeProgressElapsedMs(evt.ElapsedMs); + if (normalizedElapsedMs > 0) { - var elapsed = TimeSpan.FromMilliseconds(evt.ElapsedMs); + var elapsed = TimeSpan.FromMilliseconds(normalizedElapsedMs); if (elapsed.TotalHours >= 1) parts.Add($"{(int)elapsed.TotalHours}h {elapsed.Minutes}m"); else if (elapsed.TotalMinutes >= 1) @@ -418,11 +438,12 @@ public partial class ChatWindow bool liveWaitingStyle, out Border pulseMarker) { + var liveAccentColor = ResolveLiveProgressAccentColor(accentBrush); var cardBackground = liveWaitingStyle - ? new SolidColorBrush(Color.FromArgb(0x20, 0xF5, 0x73, 0x16)) + ? new SolidColorBrush(Color.FromArgb(0x16, liveAccentColor.R, liveAccentColor.G, liveAccentColor.B)) : Brushes.Transparent; var cardBorder = liveWaitingStyle - ? new SolidColorBrush(Color.FromArgb(0x4D, 0xF5, 0x73, 0x16)) + ? new SolidColorBrush(Color.FromArgb(0x46, liveAccentColor.R, liveAccentColor.G, liveAccentColor.B)) : Brushes.Transparent; pulseMarker = new Border @@ -1003,11 +1024,12 @@ public partial class ChatWindow Grid.SetColumn(headerLeft, 0); var headerRight = new StackPanel { Orientation = Orientation.Horizontal }; - if (logLevel != "simple" && evt.ElapsedMs > 0) + var normalizedElapsedMs = NormalizeProgressElapsedMs(evt.ElapsedMs); + if (logLevel != "simple" && normalizedElapsedMs > 0) { headerRight.Children.Add(new TextBlock { - Text = evt.ElapsedMs < 1000 ? $"{evt.ElapsedMs}ms" : $"{evt.ElapsedMs / 1000.0:F1}s", + Text = normalizedElapsedMs < 1000 ? $"{normalizedElapsedMs}ms" : $"{normalizedElapsedMs / 1000.0:F1}s", FontSize = 8.5, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, diff --git a/src/AxCopilot/Views/ChatWindow.xaml b/src/AxCopilot/Views/ChatWindow.xaml index 3b126f5..9bd7b67 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml +++ b/src/AxCopilot/Views/ChatWindow.xaml @@ -1917,7 +1917,7 @@ Visibility="Collapsed" Margin="0,0,0,6"/> + Margin="-2" IsHitTestVisible="False"> @@ -1930,10 +1930,10 @@ - 1.5 + 1.15 - + @@ -3688,6 +3688,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index df65c07..3c7b5cb 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -5302,6 +5302,7 @@ public partial class ChatWindow : Window ForceScrollToEnd(); // 사용자 메시지 전송 시 강제 하단 이동 PlayRainbowGlow(); // 무지개 글로우 애니메이션 + _streamStartTime = DateTime.UtcNow; _isStreaming = true; _streamRunTab = runTab; StartLiveAgentProgressHints(); @@ -5318,7 +5319,6 @@ public partial class ChatWindow : Window _displayedLength = 0; _cursorVisible = true; _aiIconPulseStopped = false; - _streamStartTime = DateTime.UtcNow; _elapsedTimer.Start(); SetStatus("응답 생성 중...", spinning: true); @@ -6415,7 +6415,8 @@ public partial class ChatWindow : Window var normalizedSummary = string.IsNullOrWhiteSpace(summary) ? null : summary.Trim(); var currentSummary = _liveAgentProgressHint?.Summary; var currentToolName = _liveAgentProgressHint?.ToolName ?? ""; - var elapsedMs = _isStreaming + var hasValidStreamStart = _streamStartTime.Year >= 2000 && _streamStartTime <= DateTime.UtcNow.AddSeconds(1); + var elapsedMs = _isStreaming && hasValidStreamStart ? Math.Max(0L, (long)(DateTime.UtcNow - _streamStartTime.ToUniversalTime()).TotalMilliseconds) : 0L; var inputTokens = Math.Max(0, _agentCumulativeInputTokens); @@ -8769,22 +8770,18 @@ public partial class ChatWindow : Window _rainbowTimer?.Stop(); _rainbowStartTime = DateTime.UtcNow; - // 페이드인 (빠르게) + InputGlowBorder.Effect = new System.Windows.Media.Effects.BlurEffect { Radius = 6 }; InputGlowBorder.BeginAnimation(UIElement.OpacityProperty, - new System.Windows.Media.Animation.DoubleAnimation(0, 0.9, TimeSpan.FromMilliseconds(150))); + new System.Windows.Media.Animation.DoubleAnimation(0, 0.62, TimeSpan.FromMilliseconds(180))); - // 그라데이션 회전 타이머 (~60fps) — 스트리밍 종료까지 지속 - _rainbowTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) }; + _rainbowTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(40) }; _rainbowTimer.Tick += (_, _) => { var elapsed = (DateTime.UtcNow - _rainbowStartTime).TotalMilliseconds; - - // 그라데이션 오프셋 회전 - var shift = (elapsed / 1500.0) % 1.0; // 1.5초에 1바퀴 (느리게) + var shift = (elapsed / 2000.0) % 1.0; var brush = InputGlowBorder.BorderBrush as LinearGradientBrush; if (brush == null) return; - // 시작/끝점 회전 (원형 이동) var angle = shift * Math.PI * 2; brush.StartPoint = new Point(0.5 + 0.5 * Math.Cos(angle), 0.5 + 0.5 * Math.Sin(angle)); brush.EndPoint = new Point(0.5 - 0.5 * Math.Cos(angle), 0.5 - 0.5 * Math.Sin(angle)); @@ -10278,6 +10275,9 @@ public partial class ChatWindow : Window llm.WorkflowVisualizer = ChkOverlayWorkflowVisualizer?.IsChecked == true; llm.ShowTotalCallStats = ChkOverlayShowTotalCallStats?.IsChecked == true; llm.EnableAuditLog = ChkOverlayEnableAuditLog?.IsChecked == true; + llm.EnableChatRainbowGlow = ChkOverlayEnableChatRainbowGlow?.IsChecked == true; + _settings.Settings.Launcher.EnableRainbowGlow = ChkOverlayEnableLauncherRainbowGlow?.IsChecked == true; + _settings.Settings.Launcher.EnableSelectionGlow = ChkOverlayEnableSelectionGlow?.IsChecked == true; CommitOverlayEndpointInput(normalizeOnInvalid: true); CommitOverlayApiKeyInput(); @@ -10482,6 +10482,12 @@ public partial class ChatWindow : Window ChkOverlayShowTotalCallStats.IsChecked = llm.ShowTotalCallStats; if (ChkOverlayEnableAuditLog != null) ChkOverlayEnableAuditLog.IsChecked = llm.EnableAuditLog; + if (ChkOverlayEnableChatRainbowGlow != null) + ChkOverlayEnableChatRainbowGlow.IsChecked = llm.EnableChatRainbowGlow; + if (ChkOverlayEnableLauncherRainbowGlow != null) + ChkOverlayEnableLauncherRainbowGlow.IsChecked = _settings.Settings.Launcher.EnableRainbowGlow; + if (ChkOverlayEnableSelectionGlow != null) + ChkOverlayEnableSelectionGlow.IsChecked = _settings.Settings.Launcher.EnableSelectionGlow; } RefreshOverlayThemeCards(); diff --git a/src/AxCopilot/Views/SettingsWindow.xaml b/src/AxCopilot/Views/SettingsWindow.xaml index a658cd5..835a7bf 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml +++ b/src/AxCopilot/Views/SettingsWindow.xaml @@ -1195,52 +1195,6 @@ - - - - - - - - - - - - - AX Commander 테두리에 무지개빛 회전 애니메이션을 표시합니다. - GPU 가속을 사용하므로 저사양 PC에서는 끄는 것을 권장합니다. - - - - - - - - - - - - - - - - - - - - - - - - - - - - -