fix(trajectory): show exact millisecond durations consistently

Drop the details-panel Duration toggle: Duration rows always show
integer milliseconds, matching the cell time column. Timeline labels
(Total/TTFT/Decoding) and step-group descriptions previously fell back
to second labels at or above one second; they now also show exact
milliseconds via the shared formatDurationMillis formatter.
This commit is contained in:
_Kerman
2026-08-05 16:46:23 +08:00
parent a00b11be76
commit c634fc2917
8 files changed
+49 -56

No files matched your search

@@ -299,26 +299,6 @@ function StartedAtValue({ timestamp }: { timestamp: number | null }) {
)
}
function DurationValue({ seconds }: { seconds: number | null }) {
const [showMillis, setShowMillis] = useState(false)
if (seconds === null || !Number.isFinite(seconds)) return <dd></dd>
return (
<dd>
<button
type="button"
className={css.timestampToggle}
title={showMillis ? 'Show readable duration' : 'Show exact milliseconds'}
onClick={(event) => {
if (clickSelectsText(event.currentTarget)) return
setShowMillis(current => !current)
}}
>
{showMillis ? `${Math.round(seconds * 1000)} ms` : formatElapsedSeconds(seconds)}
</button>
</dd>
)
}
function totalTime(metrics: AssistantMetricDetail): string {
if (!metrics.timingRecorded) return 'Not recorded'
if (metrics.stepStartTime === null) return 'Step start unavailable'
@@ -1376,7 +1356,7 @@ function RecordTiming({ record }: { record: TableRecord }) {
: (
<dl className={css.overview}>
<div><dt>Started</dt><StartedAtValue timestamp={record.cell.startedAt ?? null} /></div>
<div><dt>Duration</dt><DurationValue seconds={record.cell.timeSeconds} /></div>
<div><dt>Duration</dt><dd>{formatElapsedSeconds(record.cell.timeSeconds)}</dd></div>
<div><dt>Timing source</dt><dd>{record.cell.timeSeconds === null ? 'Not available' : 'Session timestamps'}</dd></div>
</dl>
)
@@ -1399,7 +1379,7 @@ function RequestTiming({
return (
<dl className={css.overview}>
<div><dt>Started</dt><StartedAtValue timestamp={request.startedAt} /></div>
<div><dt>Duration</dt><DurationValue seconds={duration} /></div>
<div><dt>Duration</dt><dd>{formatElapsedSeconds(duration)}</dd></div>
<div>
<dt>Timing source</dt>
<dd>{duration === null ? 'Session timestamps (running)' : 'Session timestamps'}</dd>
@@ -1413,7 +1393,7 @@ function RequestTiming({
<dt>Started</dt>
<StartedAtValue timestamp={anchor?.cell.startedAt ?? null} />
</div>
<div><dt>Duration</dt><DurationValue seconds={null} /></div>
<div><dt>Duration</dt><dd>{formatElapsedSeconds(null)}</dd></div>
</dl>
)
}
@@ -2757,7 +2737,7 @@ export function TrajectoryTable({
</div>
<div>
<dt>Duration</dt>
<DurationValue seconds={selected.cell.timeSeconds} />
<dd>{formatElapsedSeconds(selected.cell.timeSeconds)}</dd>
</div>
<div>
<dt>Tokens</dt>
@@ -2872,7 +2852,7 @@ export function TrajectoryTable({
{(selected.cell.kind === 'user' || selected.cell.kind === 'context') && (
<div>
<dt>Duration</dt>
<DurationValue seconds={selected.cell.timeSeconds} />
<dd>{formatElapsedSeconds(selected.cell.timeSeconds)}</dd>
</div>
)}
</dl>
@@ -1,6 +1,7 @@
/** Operation-sequence and recorded-time projections for the trajectory overview. */
import type { TrajectoryTurnModel } from './layout.ts'
import { formatDurationMillis } from './trajectory-record.ts'
import type { TrajectoryCellKind, TrajectoryCellProps } from './trajectory-record.ts'
/** Horizontal projection used by the trajectory timeline. */
@@ -34,14 +35,12 @@ export interface TrajectoryTimelineModel extends TrajectoryTimeRange {
}
/**
* Format a timeline duration with a compact unit.
* Format a timeline duration as an integer-millisecond label.
* @param milliseconds - Non-negative duration in milliseconds.
* @returns Millisecond or second label.
* @returns Millisecond label with thousands separators.
*/
export function formatTimelineOffset(milliseconds: number): string {
if (milliseconds < 1_000) return `${Math.round(milliseconds)} ms`
const seconds = milliseconds / 1_000
return seconds >= 10 ? `${Math.round(seconds)} s` : `${seconds.toFixed(1)} s`
return formatDurationMillis(milliseconds)
}
function laneFor(kind: TrajectoryCellKind): number {
@@ -106,15 +106,20 @@ export function trajectoryRecordId(cell: TrajectoryCellProps): string {
}
/**
* Format a duration with a precision that matches its magnitude.
* Format a duration in milliseconds with thousands separators.
* @param milliseconds - Duration in milliseconds, or `null` when absent.
* @returns `—` when unknown, otherwise an integer-millisecond label.
*/
export function formatDurationMillis(milliseconds: number | null): string {
if (milliseconds === null || !Number.isFinite(milliseconds)) return '—'
return `${Math.round(milliseconds).toLocaleString('en-US')} ms`
}
/**
* Format an elapsed duration given in seconds as a millisecond label.
* @param seconds - Duration seconds, or `null` when absent.
* @returns `—` when unknown, otherwise an integer-millisecond label
* below one second and a tenth-of-a-second label at or above it.
* @returns `—` when unknown, otherwise an integer-millisecond label.
*/
export function formatElapsedSeconds(seconds: number | null): string {
if (seconds === null || !Number.isFinite(seconds)) return '—'
if (seconds < 1) return `${Math.round(seconds * 1000)} ms`
const rounded = Math.round(seconds * 10) / 10
if (Number.isInteger(rounded)) return `${rounded} s`
return `${rounded.toFixed(1)} s`
return formatDurationMillis(seconds === null ? null : seconds * 1000)
}