Skip to content

feat(tui): live per-message response timing#20220

Open
want2sleeep wants to merge 2 commits intoanomalyco:devfrom
want2sleeep:feat/message-timing
Open

feat(tui): live per-message response timing#20220
want2sleeep wants to merge 2 commits intoanomalyco:devfrom
want2sleeep:feat/message-timing

Conversation

@want2sleeep
Copy link
Copy Markdown

@want2sleeep want2sleeep commented Mar 31, 2026

Issue for this PR

Closes #10739

Type of change

  • New feature

What does this PR do?

Add live elapsed time display for each assistant message during streaming. The timer shows end-to-end duration from when the user sent the message to when the assistant finishes responding.

Key behaviors:

  • Real-time updates every second during streaming
  • Hidden during first second to avoid millisecond flash
  • Format: 1s, 2s, 59s, 1m, 1m 30s, 2h 15m, 1d 3h
  • Stops updating once the message completes (including tool-calls finish)

How did you verify your code works?

Ran bun dev locally, sent messages and observed:

  • Timer starts at 1s and increments during streaming
  • No 0s flash for sub-second completions
  • Stops updating after completion
  • Handles hour/day durations correctly

Screenshots / recordings

Before:
before

After:
after

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Note: e2e test failures are in packages/app (web UI) and are pre-existing on dev. This PR only touches packages/opencode (TUI).

Copilot AI review requested due to automatic review settings March 31, 2026 07:44
@github-actions github-actions bot added the needs:compliance This means the issue will auto-close after 2 hours. label Mar 31, 2026
@want2sleeep want2sleeep changed the title feat(tui): live per-message response timing & git branch display in sidebar feat(tui): live per-message response timing Mar 31, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a live, per-assistant-message elapsed-time display in the TUI session view during streaming, intended to show end-to-end latency from the parent user message to assistant completion.

Changes:

  • Introduces a 1s ticking “now” signal during non-final assistant messages to compute live elapsed time.
  • Replaces Locale.duration(...) with a custom duration formatter for the assistant message header.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (!props.message.time.completed) return 0
const [now, setNow] = createSignal(Date.now())
createEffect(() => {
if (final()) return
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The streaming timer interval is started whenever final() is false, but messages with finish: "tool-calls" / "unknown" are intentionally non-final and still get time.completed set (e.g. tool execution). In that case this interval will run indefinitely and keep updating now even though the elapsed time is already fixed. Consider gating the interval on props.message.time.completed (or equivalent streaming state) rather than final(), so it stops once the assistant message has a completion timestamp.

Suggested change
if (final()) return
const completed = props.message.time?.completed
if (final() || completed) return

Copilot uses AI. Check for mistakes.
Comment on lines +1349 to +1353
const total = Math.floor(ms / 1000)
if (total < 60) return `${total}s`
const m = Math.floor(total / 60)
const s = total % 60
return `${m}m ${s}s`
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

durationText() formatting can display surprising values: (1) if the assistant completes in <1s, duration() becomes a small non-zero ms value and the UI will show 0s; (2) when the seconds remainder is 0 it renders "{m}m 0s" instead of "{m}m" as described in the PR; (3) durations >= 1 hour lose the existing hour/day formatting previously provided by Locale.duration. Consider adjusting the formatting/hide-threshold so sub-second completions don’t show 0s, omit 0s when s === 0, and keep hour/day support (either by extending this formatter or reusing Locale.duration with rounding).

Suggested change
const total = Math.floor(ms / 1000)
if (total < 60) return `${total}s`
const m = Math.floor(total / 60)
const s = total % 60
return `${m}m ${s}s`
const totalSeconds = Math.floor(ms / 1000)
// Hide sub-second durations instead of showing "0s"
if (totalSeconds === 0) return ""
if (totalSeconds < 60) {
return `${totalSeconds}s`
}
const totalMinutes = Math.floor(totalSeconds / 60)
const seconds = totalSeconds % 60
// Less than 1 hour: show minutes and (optionally) seconds
if (totalMinutes < 60) {
if (seconds === 0) return `${totalMinutes}m`
return `${totalMinutes}m ${seconds}s`
}
const totalHours = Math.floor(totalMinutes / 60)
const minutes = totalMinutes % 60
// Less than 1 day: show hours and (optionally) minutes
if (totalHours < 24) {
if (minutes === 0) return `${totalHours}h`
return `${totalHours}h ${minutes}m`
}
const days = Math.floor(totalHours / 24)
const hours = totalHours % 24
// 1 day or more: show days and (optionally) hours
if (hours === 0) return `${days}d`
return `${days}d ${hours}h`

Copilot uses AI. Check for mistakes.
Comment on lines +1334 to +1338
const elapsed = createMemo(() => {
const user = messages().find((x) => x.role === "user" && x.id === props.message.parentID)
if (!user || !user.time) return 0
return props.message.time.completed - user.time.created
const end = props.message.time.completed ?? now()
return end - user.time.created
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elapsed() recomputes by scanning the full messages() array to find the parent user message. While streaming, this memo will update every second; with long sessions this becomes a repeated O(n) search per tick. Consider caching the parent user created timestamp (e.g., memoize the parent message lookup once per assistant message, or index messages by id) so timer ticks don’t repeatedly traverse the full message list.

Copilot uses AI. Check for mistakes.
Comment on lines 1411 to 1415
<span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
<span style={{ fg: theme.textMuted }}> · {props.message.modelID}</span>
<Show when={duration()}>
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
<span style={{ fg: theme.textMuted }}> · {durationText()}</span>
</Show>
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions adding git branch display in the sidebar footer, but the current change set shown here only updates per-message timing in session/index.tsx. If the sidebar footer work is intended to be part of this PR, it looks like the related file changes may be missing from the branch/commit set (or from the PR).

Copilot uses AI. Check for mistakes.
@github-actions github-actions bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Mar 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@want2sleeep
Copy link
Copy Markdown
Author

Note: The e2e test failures are in packages/app (web UI) and are pre-existing on the dev branch. This PR only touches packages/opencode (TUI). Unit tests and typecheck pass.

@want2sleeep
Copy link
Copy Markdown
Author

Before / After

Before:
before

After:
after

  • Before: No elapsed time shown during streaming
  • After: Live timer updates every second (1s, 2s, 59s, 1m, 1m 30s...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Show a live running timer for prompts in the TUI

2 participants