AX Agent 진행 시간 표기와 글로우 설정 위치를 정리한다\n\n- Cowork/Code 진행 카드에서 스트리밍 시작 시각이 준비되기 전 계산되며 수천만 시간으로 튀는 문제를 수정한다\n- 진행 카드 라이브 대기 색상을 테마 AccentColor 기반으로 조정해 주황색 고정 느낌을 제거한다\n- 채팅 입력창 글로우를 런처와 같은 리듬의 무지개 글로우로 완화하고 외곽선 두께와 블러를 정리한다\n- 일반 설정의 런처/채팅 글로우 토글을 제거하고 AX Agent 내부 설정 공통 탭으로 이동한다\n- README와 DEVELOPMENT 문서에 변경 내용과 시각을 반영한다\n\n검증 결과\n- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\\n- 경고 0 / 오류 0

This commit is contained in:
2026-04-07 08:52:37 +09:00
parent f8baea24f5
commit a686d822e7
6 changed files with 165 additions and 71 deletions

View File

@@ -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 내부 설정으로 이동해, 이제 내부 설정에서 바로 런처/입력창 글로우를 함께 조정할 수 있습니다.

View File

@@ -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를 일원화했다.

View File

@@ -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<string>();
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<string>();
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,

View File

@@ -1917,7 +1917,7 @@
Visibility="Collapsed"
Margin="0,0,0,6"/>
<Border x:Name="InputGlowBorder" CornerRadius="18" Opacity="0"
Margin="-1" IsHitTestVisible="False">
Margin="-2" IsHitTestVisible="False">
<Border.BorderBrush>
<LinearGradientBrush x:Name="RainbowBrush" StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#FF6B6B" Offset="0.0"/>
@@ -1930,10 +1930,10 @@
</LinearGradientBrush>
</Border.BorderBrush>
<Border.BorderThickness>
<Thickness>1.5</Thickness>
<Thickness>1.15</Thickness>
</Border.BorderThickness>
<Border.Effect>
<BlurEffect Radius="2"/>
<BlurEffect Radius="6"/>
</Border.Effect>
</Border>
<!-- 실제 입력 영역 -->
@@ -3688,6 +3688,99 @@
</ComboBox>
</Grid>
</Border>
<Border x:Name="OverlaySectionGlowEffects"
Background="Transparent"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="0,1,0,0"
CornerRadius="0"
Padding="0,12,0,0"
Margin="0,0,0,12">
<StackPanel>
<TextBlock Text="글로우 효과"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="런처와 AX Agent의 글로우 연출을 여기서 조정합니다."
Margin="0,4,0,10"
FontSize="11"
Foreground="{DynamicResource SecondaryText}"/>
<Border Style="{StaticResource OverlayAdvancedToggleRowStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,16,0">
<TextBlock Text="런처 무지개 글로우"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="AX Commander 테두리에 무지개 글로우를 표시합니다."
Margin="0,4,0,0"
FontSize="11.5"
TextWrapping="Wrap"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<CheckBox x:Name="ChkOverlayEnableLauncherRainbowGlow"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource ToggleSwitch}"
Checked="ChkOverlayFeatureToggle_Changed"
Unchecked="ChkOverlayFeatureToggle_Changed"/>
</Grid>
</Border>
<Border Style="{StaticResource OverlayAdvancedToggleRowStyle}" Margin="0,8,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,16,0">
<TextBlock Text="런처 선택 글로우"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="선택된 런처 항목에 은은한 글로우를 표시합니다."
Margin="0,4,0,0"
FontSize="11.5"
TextWrapping="Wrap"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<CheckBox x:Name="ChkOverlayEnableSelectionGlow"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource ToggleSwitch}"
Checked="ChkOverlayFeatureToggle_Changed"
Unchecked="ChkOverlayFeatureToggle_Changed"/>
</Grid>
</Border>
<Border Style="{StaticResource OverlayAdvancedToggleRowStyle}" Margin="0,8,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,16,0">
<TextBlock Text="채팅 입력창 글로우"
FontSize="12.5"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryText}"/>
<TextBlock Text="메시지 전송 중 입력창에 런처와 같은 무지개 글로우를 표시합니다."
Margin="0,4,0,0"
FontSize="11.5"
TextWrapping="Wrap"
Foreground="{DynamicResource SecondaryText}"/>
</StackPanel>
<CheckBox x:Name="ChkOverlayEnableChatRainbowGlow"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource ToggleSwitch}"
Checked="ChkOverlayFeatureToggle_Changed"
Unchecked="ChkOverlayFeatureToggle_Changed"/>
</Grid>
</Border>
</StackPanel>
</Border>
<Border x:Name="OverlayToggleImageInput" Style="{StaticResource OverlayAdvancedToggleRowStyle}">
<Grid>
<Grid.ColumnDefinitions>

View File

@@ -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();

View File

@@ -1195,52 +1195,6 @@
<!-- 테마 선택 패널 -->
<ScrollViewer x:Name="ThemeSelectPanel" Grid.Row="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel>
<!-- ── 시각 효과 ── -->
<TextBlock Text="시각 효과" Style="{StaticResource SectionHeader}"/>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource RowLabel}" Text="런처 무지개 글로우"/>
<Border Width="16" Height="16" CornerRadius="8" Background="{DynamicResource ItemHoverBackground}" Margin="6,0,0,0" Cursor="Help" VerticalAlignment="Center">
<TextBlock Text="?" FontSize="10" FontWeight="Bold" Foreground="{DynamicResource AccentColor}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Border.ToolTip>
<ToolTip Style="{StaticResource HelpTooltipStyle}">
<TextBlock TextWrapping="Wrap" Foreground="White" FontSize="12" LineHeight="18">
AX Commander 테두리에 무지개빛 회전 애니메이션을 표시합니다.
<LineBreak/>GPU 가속을 사용하므로 저사양 PC에서는 끄는 것을 권장합니다.
</TextBlock>
</ToolTip>
</Border.ToolTip>
</Border>
</StackPanel>
<TextBlock Style="{StaticResource RowHint}" Text="AX Commander 테두리에 무지개빛 글로우 효과를 표시합니다. 저사양 PC에서는 끄는 것을 권장합니다."/>
</StackPanel>
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding EnableRainbowGlow, Mode=TwoWay}"/>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
<TextBlock Style="{StaticResource RowLabel}" Text="선택 아이템 글로우"/>
<TextBlock Style="{StaticResource RowHint}" Text="선택된 항목에 은은한 글로우를 상시 표시합니다. 테마 AccentColor를 기반으로 색상이 적용됩니다."/>
</StackPanel>
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding EnableSelectionGlow, Mode=TwoWay}"/>
</Grid>
</Border>
<Border Style="{StaticResource SettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
<TextBlock Style="{StaticResource RowLabel}" Text="채팅 입력창 무지개 글로우"/>
<TextBlock Style="{StaticResource RowHint}" Text="메시지 전송 시 입력창 테두리에 무지개빛 글로우 효과를 표시합니다. 저사양 PC에서는 끄는 것을 권장합니다."/>
</StackPanel>
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding EnableChatRainbowGlow, Mode=TwoWay}"/>
</Grid>
</Border>
<TextBlock Text="테마 선택" Style="{StaticResource SectionHeader}"/>
<Border Background="White" CornerRadius="10" Padding="14,10" Margin="0,0,0,14">
<StackPanel Orientation="Horizontal">