AX Agent 라이브 진행 메시지에 경과 시간·토큰 표시 추가\n\n- Cowork/Code 장기 대기와 컨텍스트 압축 상황을 transcript에서 더 명확히 보이도록 라이브 진행 힌트 이벤트를 확장\n- 라이브 진행 줄 우측에 경과 시간과 누적 토큰 수를 표시해 사용자가 멈춤과 처리 중 상태를 구분할 수 있게 조정\n- process feed 요약줄 스타일을 보강해 claw-code 레퍼런스처럼 대기/압축 상태가 더 눈에 잘 들어오도록 개선\n- README 및 DEVELOPMENT 문서에 2026-04-06 22:42 (KST) 기준 변경 이력 반영\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:
@@ -1371,3 +1371,6 @@ MIT License
|
|||||||
- 업데이트: 2026-04-06 22:31 (KST)
|
- 업데이트: 2026-04-06 22:31 (KST)
|
||||||
- AX Agent의 중간 처리 메시지 형식을 `claw-code`에 더 가깝게 재정리했습니다. `Thinking / ToolCall / StepStart / Planning` 계열은 작은 상태칩보다 `요약줄 + 본문 설명` 구조로 보이게 바꿔, 장시간 작업 중 “무슨 작업을 하고 있는지”를 일반 메시지처럼 읽을 수 있게 했습니다.
|
- AX Agent의 중간 처리 메시지 형식을 `claw-code`에 더 가깝게 재정리했습니다. `Thinking / ToolCall / StepStart / Planning` 계열은 작은 상태칩보다 `요약줄 + 본문 설명` 구조로 보이게 바꿔, 장시간 작업 중 “무슨 작업을 하고 있는지”를 일반 메시지처럼 읽을 수 있게 했습니다.
|
||||||
- 진행 메시지의 요약줄은 좌측 chevron과 보조 텍스트 중심으로 정리하고, 상세 설명은 바로 아래 본문 텍스트로 분리해 `claw-code / Claude Code`의 처리 메시지 밀도와 감각에 더 가깝게 맞췄습니다.
|
- 진행 메시지의 요약줄은 좌측 chevron과 보조 텍스트 중심으로 정리하고, 상세 설명은 바로 아래 본문 텍스트로 분리해 `claw-code / Claude Code`의 처리 메시지 밀도와 감각에 더 가깝게 맞췄습니다.
|
||||||
|
- 업데이트: 2026-04-06 22:42 (KST)
|
||||||
|
- AX Agent의 라이브 대기/압축 진행 힌트를 `claw-code`처럼 더 읽기 쉬운 진행 한 줄로 보강했습니다. 이제 Cowork/Code에서 오래 걸릴 때 `처리 중...`, `컨텍스트 압축 중...` 같은 요약줄이 transcript에 살아 있는 진행 상태로 나타납니다.
|
||||||
|
- 같은 진행 줄 우측에는 `경과 시간`과 `현재 누적 토큰`이 함께 표시되어, 사용자가 “지금 멈춘 건지 아직 처리 중인지”를 기다릴 근거와 함께 바로 확인할 수 있게 조정했습니다.
|
||||||
|
|||||||
@@ -5066,3 +5066,12 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
|||||||
- 기존의 작은 pill 카드 대신 `chevron 요약줄 + 본문 설명` 구조로 렌더
|
- 기존의 작은 pill 카드 대신 `chevron 요약줄 + 본문 설명` 구조로 렌더
|
||||||
- 장시간 작업 중에도 단계/툴 실행/생각 중 상태가 일반 assistant 진행 메시지처럼 읽히도록 정리
|
- 장시간 작업 중에도 단계/툴 실행/생각 중 상태가 일반 assistant 진행 메시지처럼 읽히도록 정리
|
||||||
- permission/result 카드와 진행 중 feed를 분리해, 실제 처리 메시지는 `claude-code / Claude Code`처럼 더 담백한 transcript 흐름으로 보이게 조정했다.
|
- permission/result 카드와 진행 중 feed를 분리해, 실제 처리 메시지는 `claude-code / Claude Code`처럼 더 담백한 transcript 흐름으로 보이게 조정했다.
|
||||||
|
|
||||||
|
## 2026-04-06 22:42 (KST)
|
||||||
|
|
||||||
|
- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 라이브 진행 힌트 이벤트에 `ElapsedMs`, `InputTokens`, `OutputTokens`를 실시간으로 채워 넣도록 확장했다.
|
||||||
|
- Cowork/Code에서 스트리밍 중 오래 걸릴 때마다 현재 경과 시간과 누적 토큰 수가 진행 힌트 자체에 반영된다.
|
||||||
|
- 대기/압축 힌트는 초 단위 경과 시간이나 토큰 수가 바뀌면 자동 갱신되도록 비교 조건도 함께 보강했다.
|
||||||
|
- [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)의 process feed 요약줄에 우측 메타 영역을 추가했다.
|
||||||
|
- `처리 중...`, `컨텍스트 압축 중...` 같은 라이브 진행 줄에 `6m 46s · 38.8K tokens`처럼 기다릴 근거가 함께 노출된다.
|
||||||
|
- 라이브 대기/압축 상태는 기존 chevron 대신 강조 마커와 조금 더 진한 제목 톤을 써서 일반 이벤트보다 눈에 잘 들어오도록 구분했다.
|
||||||
|
|||||||
@@ -10,7 +10,15 @@ namespace AxCopilot.Views;
|
|||||||
|
|
||||||
public partial class ChatWindow
|
public partial class ChatWindow
|
||||||
{
|
{
|
||||||
private Border CreateCompactEventPill(string summary, Brush primaryText, Brush secondaryText, Brush hintBg, Brush borderBrush, Brush accentBrush)
|
private Border CreateCompactEventPill(
|
||||||
|
string summary,
|
||||||
|
Brush primaryText,
|
||||||
|
Brush secondaryText,
|
||||||
|
Brush hintBg,
|
||||||
|
Brush borderBrush,
|
||||||
|
Brush accentBrush,
|
||||||
|
string? metaText = null,
|
||||||
|
bool liveWaitingStyle = false)
|
||||||
{
|
{
|
||||||
return new Border
|
return new Border
|
||||||
{
|
{
|
||||||
@@ -21,27 +29,49 @@ public partial class ChatWindow
|
|||||||
Padding = new Thickness(0),
|
Padding = new Thickness(0),
|
||||||
Margin = new Thickness(12, 4, 12, 2),
|
Margin = new Thickness(12, 4, 12, 2),
|
||||||
HorizontalAlignment = HorizontalAlignment.Left,
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
Child = new StackPanel
|
Child = new Grid
|
||||||
{
|
{
|
||||||
Orientation = Orientation.Horizontal,
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new ColumnDefinition { Width = GridLength.Auto },
|
||||||
|
},
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
new TextBlock
|
new StackPanel
|
||||||
{
|
{
|
||||||
Text = "›",
|
Orientation = Orientation.Horizontal,
|
||||||
FontSize = 12,
|
Children =
|
||||||
Foreground = secondaryText,
|
{
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
new TextBlock
|
||||||
Margin = new Thickness(0, 0, 6, 0),
|
{
|
||||||
|
Text = liveWaitingStyle ? "✶" : "›",
|
||||||
|
FontSize = liveWaitingStyle ? 11.5 : 12,
|
||||||
|
Foreground = liveWaitingStyle ? accentBrush : secondaryText,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Margin = new Thickness(0, 0, 6, 0),
|
||||||
|
FontWeight = liveWaitingStyle ? FontWeights.SemiBold : FontWeights.Normal,
|
||||||
|
},
|
||||||
|
new TextBlock
|
||||||
|
{
|
||||||
|
Text = summary,
|
||||||
|
FontSize = liveWaitingStyle ? 11.5 : 11,
|
||||||
|
FontWeight = liveWaitingStyle ? FontWeights.SemiBold : FontWeights.Normal,
|
||||||
|
Foreground = liveWaitingStyle ? primaryText : secondaryText,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TextBlock
|
new TextBlock
|
||||||
{
|
{
|
||||||
Text = summary,
|
Text = metaText ?? "",
|
||||||
FontSize = 11,
|
FontSize = 10,
|
||||||
FontWeight = FontWeights.Normal,
|
FontWeight = liveWaitingStyle ? FontWeights.Medium : FontWeights.Normal,
|
||||||
Foreground = secondaryText,
|
Foreground = secondaryText,
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
TextWrapping = TextWrapping.Wrap,
|
Margin = new Thickness(10, 0, 0, 0),
|
||||||
|
Visibility = string.IsNullOrWhiteSpace(metaText) ? Visibility.Collapsed : Visibility.Visible,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,6 +112,10 @@ public partial class ChatWindow
|
|||||||
{
|
{
|
||||||
return evt.Type switch
|
return evt.Type switch
|
||||||
{
|
{
|
||||||
|
AgentEventType.Thinking when string.Equals(evt.ToolName, "agent_wait", StringComparison.OrdinalIgnoreCase)
|
||||||
|
=> "처리 중...",
|
||||||
|
AgentEventType.Thinking when string.Equals(evt.ToolName, "context_compaction", StringComparison.OrdinalIgnoreCase)
|
||||||
|
=> "컨텍스트 압축 중...",
|
||||||
AgentEventType.Planning when evt.Steps is { Count: > 0 }
|
AgentEventType.Planning when evt.Steps is { Count: > 0 }
|
||||||
=> $"{evt.Steps.Count}단계 계획 정리",
|
=> $"{evt.Steps.Count}단계 계획 정리",
|
||||||
AgentEventType.StepStart when evt.StepTotal > 0
|
AgentEventType.StepStart when evt.StepTotal > 0
|
||||||
@@ -111,6 +145,7 @@ public partial class ChatWindow
|
|||||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||||
|
|
||||||
|
var processMeta = BuildProcessFeedMetaText(evt);
|
||||||
var summary = BuildProcessFeedSummary(evt, transcriptBadgeLabel, itemDisplayName).Trim();
|
var summary = BuildProcessFeedSummary(evt, transcriptBadgeLabel, itemDisplayName).Trim();
|
||||||
if (string.IsNullOrWhiteSpace(summary))
|
if (string.IsNullOrWhiteSpace(summary))
|
||||||
summary = transcriptBadgeLabel;
|
summary = transcriptBadgeLabel;
|
||||||
@@ -120,7 +155,18 @@ public partial class ChatWindow
|
|||||||
Margin = new Thickness(0),
|
Margin = new Thickness(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
var summaryRow = CreateCompactEventPill(summary, primaryText, secondaryText, hintBg, borderBrush, accentBrush);
|
var liveWaitingStyle = evt.Type == AgentEventType.Thinking
|
||||||
|
&& (string.Equals(evt.ToolName, "agent_wait", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(evt.ToolName, "context_compaction", StringComparison.OrdinalIgnoreCase));
|
||||||
|
var summaryRow = CreateCompactEventPill(
|
||||||
|
summary,
|
||||||
|
primaryText,
|
||||||
|
secondaryText,
|
||||||
|
hintBg,
|
||||||
|
borderBrush,
|
||||||
|
accentBrush,
|
||||||
|
processMeta,
|
||||||
|
liveWaitingStyle);
|
||||||
summaryRow.Opacity = 0;
|
summaryRow.Opacity = 0;
|
||||||
summaryRow.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(140)));
|
summaryRow.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(140)));
|
||||||
stack.Children.Add(summaryRow);
|
stack.Children.Add(summaryRow);
|
||||||
@@ -166,6 +212,28 @@ public partial class ChatWindow
|
|||||||
MessagePanel.Children.Add(stack);
|
MessagePanel.Children.Add(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string BuildProcessFeedMetaText(AgentEvent evt)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
|
||||||
|
if (evt.ElapsedMs > 0)
|
||||||
|
{
|
||||||
|
var elapsed = TimeSpan.FromMilliseconds(evt.ElapsedMs);
|
||||||
|
if (elapsed.TotalHours >= 1)
|
||||||
|
parts.Add($"{(int)elapsed.TotalHours}h {elapsed.Minutes}m");
|
||||||
|
else if (elapsed.TotalMinutes >= 1)
|
||||||
|
parts.Add($"{(int)elapsed.TotalMinutes}m {elapsed.Seconds}s");
|
||||||
|
else
|
||||||
|
parts.Add($"{Math.Max(1, elapsed.Seconds)}s");
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalTokens = Math.Max(0, evt.InputTokens) + Math.Max(0, evt.OutputTokens);
|
||||||
|
if (totalTokens > 0)
|
||||||
|
parts.Add($"{FormatTokenCount(totalTokens)} tokens");
|
||||||
|
|
||||||
|
return string.Join(" · ", parts);
|
||||||
|
}
|
||||||
|
|
||||||
private Border CreateAgentMetaChip(string text, string icon, Brush foreground, Brush background, Brush borderBrush)
|
private Border CreateAgentMetaChip(string text, string icon, Brush foreground, Brush background, Brush borderBrush)
|
||||||
{
|
{
|
||||||
return new Border
|
return new Border
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ public partial class ChatWindow : Window
|
|||||||
private readonly DispatcherTimer _taskSummaryRefreshTimer;
|
private readonly DispatcherTimer _taskSummaryRefreshTimer;
|
||||||
private readonly DispatcherTimer _conversationPersistTimer;
|
private readonly DispatcherTimer _conversationPersistTimer;
|
||||||
private readonly DispatcherTimer _agentUiEventTimer;
|
private readonly DispatcherTimer _agentUiEventTimer;
|
||||||
|
private readonly DispatcherTimer _agentProgressHintTimer;
|
||||||
private readonly DispatcherTimer _tokenUsagePopupCloseTimer;
|
private readonly DispatcherTimer _tokenUsagePopupCloseTimer;
|
||||||
private readonly DispatcherTimer _responsiveLayoutTimer;
|
private readonly DispatcherTimer _responsiveLayoutTimer;
|
||||||
private CancellationTokenSource? _gitStatusRefreshCts;
|
private CancellationTokenSource? _gitStatusRefreshCts;
|
||||||
@@ -131,6 +132,8 @@ public partial class ChatWindow : Window
|
|||||||
private readonly Dictionary<string, ChatConversation> _pendingConversationPersists = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, ChatConversation> _pendingConversationPersists = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly HashSet<string> _expandedDraftQueueTabs = new(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _expandedDraftQueueTabs = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private AgentEvent? _pendingAgentUiEvent;
|
private AgentEvent? _pendingAgentUiEvent;
|
||||||
|
private AgentEvent? _liveAgentProgressHint;
|
||||||
|
private DateTime _lastAgentProgressEventAt = DateTime.UtcNow;
|
||||||
private double _lastResponsiveComposerWidth;
|
private double _lastResponsiveComposerWidth;
|
||||||
private double _lastResponsiveMessageWidth;
|
private double _lastResponsiveMessageWidth;
|
||||||
private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg)
|
private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg)
|
||||||
@@ -268,6 +271,8 @@ public partial class ChatWindow : Window
|
|||||||
_agentUiEventTimer.Stop();
|
_agentUiEventTimer.Stop();
|
||||||
FlushPendingAgentUiEvent();
|
FlushPendingAgentUiEvent();
|
||||||
};
|
};
|
||||||
|
_agentProgressHintTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||||
|
_agentProgressHintTimer.Tick += AgentProgressHintTimer_Tick;
|
||||||
_tokenUsagePopupCloseTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(140) };
|
_tokenUsagePopupCloseTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(140) };
|
||||||
_tokenUsagePopupCloseTimer.Tick += (_, _) =>
|
_tokenUsagePopupCloseTimer.Tick += (_, _) =>
|
||||||
{
|
{
|
||||||
@@ -5286,6 +5291,7 @@ public partial class ChatWindow : Window
|
|||||||
|
|
||||||
_isStreaming = true;
|
_isStreaming = true;
|
||||||
_streamRunTab = runTab;
|
_streamRunTab = runTab;
|
||||||
|
StartLiveAgentProgressHints();
|
||||||
BtnSend.IsEnabled = false;
|
BtnSend.IsEnabled = false;
|
||||||
BtnSend.Visibility = Visibility.Collapsed;
|
BtnSend.Visibility = Visibility.Collapsed;
|
||||||
BtnStop.Visibility = Visibility.Visible;
|
BtnStop.Visibility = Visibility.Visible;
|
||||||
@@ -5468,6 +5474,7 @@ public partial class ChatWindow : Window
|
|||||||
{
|
{
|
||||||
FlushPendingConversationPersists();
|
FlushPendingConversationPersists();
|
||||||
FlushPendingAgentUiEvent();
|
FlushPendingAgentUiEvent();
|
||||||
|
StopLiveAgentProgressHints();
|
||||||
_cursorTimer.Stop();
|
_cursorTimer.Stop();
|
||||||
_elapsedTimer.Stop();
|
_elapsedTimer.Stop();
|
||||||
_typingTimer.Stop();
|
_typingTimer.Stop();
|
||||||
@@ -6135,6 +6142,7 @@ public partial class ChatWindow : Window
|
|||||||
|
|
||||||
private void OnAgentEvent(AgentEvent evt)
|
private void OnAgentEvent(AgentEvent evt)
|
||||||
{
|
{
|
||||||
|
TouchLiveAgentProgressHints();
|
||||||
var eventTab = string.IsNullOrWhiteSpace(_streamRunTab) ? _activeTab : _streamRunTab!;
|
var eventTab = string.IsNullOrWhiteSpace(_streamRunTab) ? _activeTab : _streamRunTab!;
|
||||||
|
|
||||||
// 실행 로그는 직접 배너를 먼저 꽂지 않고, 대화 모델에 누적한 뒤 재렌더합니다.
|
// 실행 로그는 직접 배너를 먼저 꽂지 않고, 대화 모델에 누적한 뒤 재렌더합니다.
|
||||||
@@ -6164,6 +6172,127 @@ public partial class ChatWindow : Window
|
|||||||
ScheduleTaskSummaryRefresh();
|
ScheduleTaskSummaryRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartLiveAgentProgressHints()
|
||||||
|
{
|
||||||
|
_lastAgentProgressEventAt = DateTime.UtcNow;
|
||||||
|
UpdateLiveAgentProgressHint(null);
|
||||||
|
_agentProgressHintTimer.Stop();
|
||||||
|
_agentProgressHintTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopLiveAgentProgressHints()
|
||||||
|
{
|
||||||
|
_agentProgressHintTimer.Stop();
|
||||||
|
UpdateLiveAgentProgressHint(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TouchLiveAgentProgressHints()
|
||||||
|
{
|
||||||
|
_lastAgentProgressEventAt = DateTime.UtcNow;
|
||||||
|
UpdateLiveAgentProgressHint(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AgentProgressHintTimer_Tick(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isStreaming || !_agentLoop.IsRunning)
|
||||||
|
{
|
||||||
|
StopLiveAgentProgressHints();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var runTab = string.IsNullOrWhiteSpace(_streamRunTab) ? _activeTab : _streamRunTab!;
|
||||||
|
if (!string.Equals(runTab, "Cowork", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !string.Equals(runTab, "Code", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
UpdateLiveAgentProgressHint(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idle = DateTime.UtcNow - _lastAgentProgressEventAt;
|
||||||
|
string? summary = null;
|
||||||
|
var toolName = "agent_wait";
|
||||||
|
|
||||||
|
if (_pendingPostCompaction && idle >= TimeSpan.FromSeconds(2))
|
||||||
|
{
|
||||||
|
toolName = "context_compaction";
|
||||||
|
summary = idle >= TimeSpan.FromSeconds(12)
|
||||||
|
? "컨텍스트를 압축한 뒤 응답을 정리하는 중입니다..."
|
||||||
|
: "컨텍스트를 압축하고 있습니다...";
|
||||||
|
}
|
||||||
|
else if (idle >= TimeSpan.FromSeconds(12))
|
||||||
|
{
|
||||||
|
summary = "응답을 정리하는 중입니다. 아직 처리 중입니다...";
|
||||||
|
}
|
||||||
|
else if (idle >= TimeSpan.FromSeconds(5))
|
||||||
|
{
|
||||||
|
summary = "생각을 정리하는 중입니다...";
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateLiveAgentProgressHint(summary, toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLiveAgentProgressHint(string? summary, string toolName = "")
|
||||||
|
{
|
||||||
|
var normalizedSummary = string.IsNullOrWhiteSpace(summary) ? null : summary.Trim();
|
||||||
|
var currentSummary = _liveAgentProgressHint?.Summary;
|
||||||
|
var currentToolName = _liveAgentProgressHint?.ToolName ?? "";
|
||||||
|
var elapsedMs = _isStreaming
|
||||||
|
? Math.Max(0L, (long)(DateTime.UtcNow - _streamStartTime.ToUniversalTime()).TotalMilliseconds)
|
||||||
|
: 0L;
|
||||||
|
var inputTokens = Math.Max(0, _agentCumulativeInputTokens);
|
||||||
|
var outputTokens = Math.Max(0, _agentCumulativeOutputTokens);
|
||||||
|
var currentElapsedBucket = (_liveAgentProgressHint?.ElapsedMs ?? 0) / 1000;
|
||||||
|
var nextElapsedBucket = elapsedMs / 1000;
|
||||||
|
if (string.Equals(currentSummary, normalizedSummary, StringComparison.Ordinal)
|
||||||
|
&& string.Equals(currentToolName, toolName, StringComparison.Ordinal)
|
||||||
|
&& currentElapsedBucket == nextElapsedBucket
|
||||||
|
&& (_liveAgentProgressHint?.InputTokens ?? 0) == inputTokens
|
||||||
|
&& (_liveAgentProgressHint?.OutputTokens ?? 0) == outputTokens)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_liveAgentProgressHint = normalizedSummary == null
|
||||||
|
? null
|
||||||
|
: new AgentEvent
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
RunId = _appState.AgentRun.RunId,
|
||||||
|
Type = AgentEventType.Thinking,
|
||||||
|
ToolName = toolName,
|
||||||
|
Summary = normalizedSummary,
|
||||||
|
ElapsedMs = elapsedMs,
|
||||||
|
InputTokens = inputTokens,
|
||||||
|
OutputTokens = outputTokens,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (MessagePanel != null)
|
||||||
|
RenderMessages(preserveViewport: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentEvent? GetLiveAgentProgressHint()
|
||||||
|
{
|
||||||
|
if (_liveAgentProgressHint == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new AgentEvent
|
||||||
|
{
|
||||||
|
Timestamp = _liveAgentProgressHint.Timestamp,
|
||||||
|
RunId = _liveAgentProgressHint.RunId,
|
||||||
|
Type = _liveAgentProgressHint.Type,
|
||||||
|
ToolName = _liveAgentProgressHint.ToolName,
|
||||||
|
Summary = _liveAgentProgressHint.Summary,
|
||||||
|
FilePath = _liveAgentProgressHint.FilePath,
|
||||||
|
Success = _liveAgentProgressHint.Success,
|
||||||
|
StepCurrent = _liveAgentProgressHint.StepCurrent,
|
||||||
|
StepTotal = _liveAgentProgressHint.StepTotal,
|
||||||
|
Steps = _liveAgentProgressHint.Steps,
|
||||||
|
ElapsedMs = _liveAgentProgressHint.ElapsedMs,
|
||||||
|
InputTokens = _liveAgentProgressHint.InputTokens,
|
||||||
|
OutputTokens = _liveAgentProgressHint.OutputTokens,
|
||||||
|
ToolInput = _liveAgentProgressHint.ToolInput,
|
||||||
|
Iteration = _liveAgentProgressHint.Iteration,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void OnSubAgentStatusChanged(SubAgentStatusEvent evt)
|
private void OnSubAgentStatusChanged(SubAgentStatusEvent evt)
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user