AX Agent 메시지 액션과 응답 메타 표시 개선\n\n- assistant 메시지에 응답시간과 토큰 수를 저장하는 메타 필드 추가\n- 응답 커밋 시 토큰 사용량과 경과 시간을 메시지 모델에 함께 기록하도록 엔진과 전송 경로 보강\n- 사용자/assistant 메시지 액션 바를 기본 저강도 노출과 hover 강조 방식으로 바꿔 복사 편집 재생성 좋아요 싫어요가 보이도록 정리\n- README와 DEVELOPMENT 문서에 메시지 액션 및 응답 메타 개선 이력 반영\n- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0 / 오류 0)
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
This commit is contained in:
@@ -262,6 +262,15 @@ public class ChatMessage
|
||||
[JsonPropertyName("feedback")]
|
||||
public string? Feedback { get; set; }
|
||||
|
||||
[JsonPropertyName("responseElapsedMs")]
|
||||
public long? ResponseElapsedMs { get; set; }
|
||||
|
||||
[JsonPropertyName("promptTokens")]
|
||||
public int PromptTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("completionTokens")]
|
||||
public int CompletionTokens { get; set; }
|
||||
|
||||
/// <summary>첨부된 파일 경로 목록.</summary>
|
||||
[JsonPropertyName("attachedFiles")]
|
||||
public List<string>? AttachedFiles { get; set; }
|
||||
|
||||
@@ -129,12 +129,20 @@ public sealed class AxAgentExecutionEngine
|
||||
ChatConversation conversation,
|
||||
string tab,
|
||||
string content,
|
||||
int promptTokens = 0,
|
||||
int completionTokens = 0,
|
||||
long? responseElapsedMs = null,
|
||||
string? metaRunId = null,
|
||||
ChatStorageService? storage = null)
|
||||
{
|
||||
var assistant = new ChatMessage
|
||||
{
|
||||
Role = "assistant",
|
||||
Content = content,
|
||||
PromptTokens = Math.Max(0, promptTokens),
|
||||
CompletionTokens = Math.Max(0, completionTokens),
|
||||
ResponseElapsedMs = responseElapsedMs is > 0 ? responseElapsedMs : null,
|
||||
MetaRunId = string.IsNullOrWhiteSpace(metaRunId) ? null : metaRunId,
|
||||
};
|
||||
|
||||
if (session != null)
|
||||
@@ -153,13 +161,17 @@ public sealed class AxAgentExecutionEngine
|
||||
ChatConversation conversation,
|
||||
string tab,
|
||||
string? content,
|
||||
int promptTokens = 0,
|
||||
int completionTokens = 0,
|
||||
long? responseElapsedMs = null,
|
||||
string? metaRunId = null,
|
||||
ChatStorageService? storage = null)
|
||||
{
|
||||
var normalized = NormalizeAssistantContentForUi(conversation, tab, content);
|
||||
if (tab is "Cowork" or "Code")
|
||||
conversation.ShowExecutionHistory = false;
|
||||
|
||||
CommitAssistantMessage(session, conversation, tab, normalized, storage);
|
||||
CommitAssistantMessage(session, conversation, tab, normalized, promptTokens, completionTokens, responseElapsedMs, metaRunId, storage);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
@@ -4189,7 +4189,7 @@ public partial class ChatWindow : Window
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Opacity = 0,
|
||||
Opacity = 0.8,
|
||||
Margin = new Thickness(0, 2, 0, 0),
|
||||
};
|
||||
var capturedUserContent = content;
|
||||
@@ -4215,8 +4215,8 @@ public partial class ChatWindow : Window
|
||||
});
|
||||
userBottomBar.Children.Add(userActionBar);
|
||||
wrapper.Children.Add(userBottomBar);
|
||||
wrapper.MouseEnter += (_, _) => ShowMessageActionBar(userActionBar);
|
||||
wrapper.MouseLeave += (_, _) => HideMessageActionBarIfNotSelected(userActionBar);
|
||||
wrapper.MouseEnter += (_, _) => userActionBar.Opacity = 1;
|
||||
wrapper.MouseLeave += (_, _) => userActionBar.Opacity = ReferenceEquals(_selectedMessageActionBar, userActionBar) ? 1 : 0.8;
|
||||
wrapper.MouseLeftButtonUp += (_, _) => SelectMessageActionBar(userActionBar, bubble);
|
||||
|
||||
// 우클릭 → 메시지 컨텍스트 메뉴
|
||||
@@ -4412,7 +4412,7 @@ public partial class ChatWindow : Window
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Margin = new Thickness(2, 2, 0, 0),
|
||||
Opacity = 0
|
||||
Opacity = 0.8
|
||||
};
|
||||
|
||||
var btnColor = secondaryText;
|
||||
@@ -4438,8 +4438,12 @@ public partial class ChatWindow : Window
|
||||
});
|
||||
|
||||
container.Children.Add(actionBar);
|
||||
container.MouseEnter += (_, _) => ShowMessageActionBar(actionBar);
|
||||
container.MouseLeave += (_, _) => HideMessageActionBarIfNotSelected(actionBar);
|
||||
var assistantMeta = CreateAssistantMessageMetaText(message);
|
||||
if (assistantMeta != null)
|
||||
container.Children.Add(assistantMeta);
|
||||
|
||||
container.MouseEnter += (_, _) => actionBar.Opacity = 1;
|
||||
container.MouseLeave += (_, _) => actionBar.Opacity = ReferenceEquals(_selectedMessageActionBar, actionBar) ? 1 : 0.8;
|
||||
container.MouseLeftButtonUp += (_, _) => SelectMessageActionBar(actionBar, contentCard);
|
||||
|
||||
// 우클릭 → 메시지 컨텍스트 메뉴
|
||||
@@ -4846,6 +4850,40 @@ public partial class ChatWindow : Window
|
||||
actionBar.Children.Add(dislikeBtn);
|
||||
}
|
||||
|
||||
private TextBlock? CreateAssistantMessageMetaText(ChatMessage? message)
|
||||
{
|
||||
if (message == null)
|
||||
return null;
|
||||
|
||||
var parts = new List<string>();
|
||||
if (message.ResponseElapsedMs is > 0)
|
||||
{
|
||||
var elapsedMs = message.ResponseElapsedMs.Value;
|
||||
parts.Add(elapsedMs < 1000
|
||||
? $"{elapsedMs}ms"
|
||||
: $"{(elapsedMs / 1000.0):0.0}s");
|
||||
}
|
||||
|
||||
if (message.PromptTokens > 0 || message.CompletionTokens > 0)
|
||||
{
|
||||
var totalTokens = message.PromptTokens + message.CompletionTokens;
|
||||
parts.Add($"{FormatTokenCount(totalTokens)} tokens");
|
||||
}
|
||||
|
||||
if (parts.Count == 0)
|
||||
return null;
|
||||
|
||||
return new TextBlock
|
||||
{
|
||||
Text = string.Join(" · ", parts),
|
||||
FontSize = 9.75,
|
||||
Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Margin = new Thickness(4, 2, 0, 0),
|
||||
Opacity = 0.72,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── 메시지 등장 애니메이션 ──────────────────────────────────────────
|
||||
|
||||
private static void ApplyMessageEntryAnimation(FrameworkElement element)
|
||||
@@ -6623,7 +6661,7 @@ public partial class ChatWindow : Window
|
||||
lock (_convLock)
|
||||
{
|
||||
var session = ChatSession;
|
||||
_chatEngine.CommitAssistantMessage(session, conv, runTab, assistantText, _storage);
|
||||
_chatEngine.CommitAssistantMessage(session, conv, runTab, assistantText, storage: _storage);
|
||||
_currentConversation = session?.CurrentConversation ?? conv;
|
||||
conv = _currentConversation!;
|
||||
}
|
||||
@@ -6658,14 +6696,14 @@ public partial class ChatWindow : Window
|
||||
if (session != null)
|
||||
{
|
||||
session.AppendMessage(runTab, userMsg, useForTitle: true);
|
||||
_chatEngine.CommitAssistantMessage(session, conv, runTab, assistantText, _storage);
|
||||
_chatEngine.CommitAssistantMessage(session, conv, runTab, assistantText, storage: _storage);
|
||||
_currentConversation = session.CurrentConversation;
|
||||
conv = _currentConversation!;
|
||||
}
|
||||
else
|
||||
{
|
||||
conv.Messages.Add(userMsg);
|
||||
_chatEngine.CommitAssistantMessage(null, conv, runTab, assistantText, _storage);
|
||||
_chatEngine.CommitAssistantMessage(null, conv, runTab, assistantText, storage: _storage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8343,6 +8381,10 @@ public partial class ChatWindow : Window
|
||||
var draftSucceeded = false;
|
||||
var draftCancelled = false;
|
||||
string? draftFailure = null;
|
||||
var responseElapsedMs = 0L;
|
||||
var promptTokens = 0;
|
||||
var completionTokens = 0;
|
||||
string? assistantMetaRunId = null;
|
||||
|
||||
_activeStreamText = null;
|
||||
_cachedStreamContent = "";
|
||||
@@ -8361,6 +8403,22 @@ public partial class ChatWindow : Window
|
||||
(messages, token) => _llm.SendAsync(messages.ToList(), token),
|
||||
_streamCts.Token);
|
||||
assistantContent = response;
|
||||
responseElapsedMs = Math.Max(0, (long)(DateTime.UtcNow - _streamStartTime).TotalMilliseconds);
|
||||
assistantMetaRunId = _appState.AgentRun.RunId;
|
||||
var usage = _llm.LastTokenUsage;
|
||||
if (usage != null)
|
||||
{
|
||||
if (runTab is "Cowork" or "Code" && (_agentCumulativeInputTokens > 0 || _agentCumulativeOutputTokens > 0))
|
||||
{
|
||||
promptTokens = Math.Max(0, _agentCumulativeInputTokens);
|
||||
completionTokens = Math.Max(0, _agentCumulativeOutputTokens);
|
||||
}
|
||||
else
|
||||
{
|
||||
promptTokens = Math.Max(0, usage.PromptTokens);
|
||||
completionTokens = Math.Max(0, usage.CompletionTokens);
|
||||
}
|
||||
}
|
||||
StopAiIconPulse();
|
||||
_cachedStreamContent = response;
|
||||
draftSucceeded = true;
|
||||
@@ -8387,7 +8445,16 @@ public partial class ChatWindow : Window
|
||||
lock (_convLock)
|
||||
{
|
||||
var session = ChatSession;
|
||||
assistantContent = _chatEngine.FinalizeAssistantTurn(session, conversation, runTab, assistantContent, _storage);
|
||||
assistantContent = _chatEngine.FinalizeAssistantTurn(
|
||||
session,
|
||||
conversation,
|
||||
runTab,
|
||||
assistantContent,
|
||||
promptTokens,
|
||||
completionTokens,
|
||||
responseElapsedMs,
|
||||
assistantMetaRunId,
|
||||
_storage);
|
||||
_currentConversation = session?.CurrentConversation ?? conversation;
|
||||
conversation = _currentConversation!;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user