mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
feat(chat): per-turn stopwatch on assistant bubbles (Phase 2.2)
Wall-clock duration of each agent turn renders as a compact pill in the message metadata footer (Mac) / below the bubble (iOS). Mirrors the per-turn stopwatch Hermes v2026.4.23's TUI rewrite ships. ScarfCore RichChatViewModel: - currentTurnStart: Date? captured in addUserMessage when entering a fresh turn (skipped for /steer-style mid-run sends so the duration reflects the FULL turn). - turnDurations: [Int: TimeInterval] keyed by finalised assistant message id; populated in finalizeStreamingMessage and cleared on reset(). - formatTurnDuration(_:) static — "0.8s" / "4.2s" / "1m 12s". Mac: - RichMessageBubble gains turnDuration: TimeInterval?; renders via formatTurnDuration in the existing metadata footer. - RichChatMessageList + MessageGroupView thread the durations dict through; RichChatView wires richChat.turnDurations. iOS: - MessageBubble gains turnDuration parameter; renders below the bubble for assistant messages only. - ChatView's ForEach passes controller.vm.turnDuration(forMessageId:). Verified: Mac + iOS builds clean. Resumed sessions (loaded from state.db) show no pill — turnDurations only populates for live ACP turns, which is the correct behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -165,8 +165,11 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
ForEach(controller.vm.messages) { msg in
|
||||
MessageBubble(message: msg)
|
||||
.id(msg.id)
|
||||
MessageBubble(
|
||||
message: msg,
|
||||
turnDuration: controller.vm.turnDuration(forMessageId: msg.id)
|
||||
)
|
||||
.id(msg.id)
|
||||
}
|
||||
if controller.vm.isGenerating {
|
||||
HStack {
|
||||
@@ -933,6 +936,11 @@ private struct PermissionWrapper: Identifiable {
|
||||
|
||||
private struct MessageBubble: View {
|
||||
let message: HermesMessage
|
||||
/// Wall-clock duration of the agent turn this assistant message
|
||||
/// belongs to (v2.5). Renders as a small `4.2s` pill below the
|
||||
/// bubble when present. Nil for user / streaming / pre-v2.5
|
||||
/// resumed messages.
|
||||
var turnDuration: TimeInterval? = nil
|
||||
|
||||
var body: some View {
|
||||
if message.isToolResult {
|
||||
@@ -963,6 +971,13 @@ private struct MessageBubble: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Per-turn stopwatch — assistant only, when the
|
||||
// turn duration was captured (live ACP turns).
|
||||
if !message.isUser, let seconds = turnDuration {
|
||||
Text(RichChatViewModel.formatTurnDuration(seconds))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
}
|
||||
if !message.isUser { Spacer(minLength: 40) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user